⬆️ ⬇️

What happens if you cross React and Angular?



Akili is a javascript framework that has been influenced by solutions such as React, Angular, Aurelia, and to a lesser extent some others. The goal was to combine all the best that I see in them and to simplify everything as much as possible.



Like React, but pushes JSX? Love Angular, but tired of all the magic?



Then you should try it.



I am convinced that the best way to sort out this practice. Therefore, I will begin the description immediately with examples. They are written as if we compiled the code using Babel (es2015 + stage-0).

')

The first steps



import Akili from 'akili'; class MyComponent extends Akili.Component { constructor(el, scope) { super(el, scope); scope.example = 'Hello World'; } } Akili.component('my-component', MyComponent); //   document.addEventListener('DOMContentLoaded', () => { Akili.init(); //   }); 


 <body> <my-component>${ this.example }</my-component> </body> 


Here we created our first component, registered it and initialized the application. The usual component approach at first glance, but I would immediately like to note a couple of points.



First, the component's scope is separated from the markup scope. That is, you can safely inherit the components and it does not affect this very markup.



 class MySecondComponent extends MyComponent { constructor(...args) { super(...args); this.scope.example = 'Goodbye World'; } myOwnMethod() {} } Akili.component('my-second-component', MySecondComponent) 


 <body> <my-component>${ this.example }</my-component> <my-second-component>${ this.example }</my-second-component> </body> 


The scope component property is responsible for the markup scope . This is a special object that you can fill with the necessary data and display them in templates using expressions like ${ this.example } , where this is this very scope. In fact, there can be any javascript expression in brackets.



Secondly, markup scopes are also inherited. Add a new value to the scope of the first component:



 class MyComponent extends Akili.Component { constructor(el, scope) { super(el, scope); scope.example = 'Hello World'; scope.test = 'Test'; } } 


Then the markup is below:



 <body> <my-component> <b>${ this.example }</b> <my-second-component>${ this.example } - ${ this.test }</my-second-component> </my-component> </body> 


After compiling it will look like:



 <body> <my-component> <b>Hello World</b> <my-second-component>Goodbye World - Test</my-second-component> </my-component> </body> 


Third, the logic of the component is synchronized with its template only by changing the scope variable at any time.



 class MyComponent extends Akili.Component { constructor(...args) { super(...args); this.scope.example = 'Hello World'; setTimeout(() => { this.scope.example = 'Goodbye World'; }, 1000); } } 


In a second, the value of the variable will change in both the object and the template.



Lifecycle in a nutshell compared to React



.constructor (el, scope)

First of all, since any component is a simple javascript class, the constructor will be called. It takes in the html arguments the element to which the component and the scope object will be bound. Here you can make any changes with the element, or cancel the compilation, if necessary, by calling the .cancel () method.



.created ()

If the compilation of the component was not canceled, then you get here. This method is actually no different from the constructor. In React, componentWillMount performs a similar function.



.compiled ()

Here the component is compiled, in templates instead of expressions, the corresponding values ​​are already.

In React, this is componentDidMount . You have access to all the parent elements, which by this time are guaranteed to be compiled too.



.resolved ()

This method, as far as I know, is unique in the framework frames I know of.

The fact is that Akili allows you to use asynchronous operations when compiling, if necessary. These include some system and any custom operations. For example, loading a component template from a file:



 class MyComponent extends Akili.Component { static templateUrl = '/my-component.html'; constructor(...args) { super(...args); this.scope.example = 'Hello World'; } } 


Or any asynchronous operation which we will execute:



 class MyComponent extends Akili.Component { static templateUrl = '/my-component.html'; constructor(...args) { super(...args); this.scope.example = 'Hello World'; } compiled() { return new Promise((res) => setTimeout(res, 1000)); } } 


In the compiled method, you can return a promise, then resolved will wait for all asynchronous operations to be performed. In this case, the compilation itself will occur synchronously.



In other words, in the resolved method, you can be sure that absolutely all the child elements of any nesting level, including those containing any asynchronous operations, are compiled.



.removed ()

Called when a component is removed. Analog - componentDidUnmount .



.changed (key, value)

Called when any attribute of a component changes. Analog - componentWillReceiveProps .

This is a very important part of the framework, so I will describe it in more detail in a separate section below.



Universality, isolation, modularity of components



It is very important that the component can be completely isolated and not at all dependent on external conditions. Here is an example of such a component:



 import Akili from 'akili'; class NineComponent extends Akili.Component { static template = '${ this.str }'; static define() { Akili.component('nine', NineComponent); } constructor(...args) { super(...args); this.scope.str = ''; } compiled() { this.attrs.hasOwnProperty('str') && this.addNine(this.attrs.str); } changed(key, value) { if(key == 'str') { this.addNine(value); } } addNine(value) { this.scope.str = value + '9'; } } 


Add it to the previous examples:



 import NineComponent from './nine-component'; NineComponent.define(); Akili.component('my-component', MyComponent); document.addEventListener('DOMContentLoaded', () => { Akili.init(); }); 


 <body> <my-component> <nine str="${ this.example }"></nine> </my-component> </body> 


So, this is what we get after compilation:



 <body> <my-component> <nine str="Hello World">Hello World9</nine> </my-component> </body> 


Please note, NineComponent turned out to be completely detached. It is similar to a function that can take some arguments and do something with them. In this case, simply adds the number 9 to the end of the transferred string and displays it.



An analogy can be drawn between the attributes in Akili and the properties in React.

this.attrs => this.props . They perform the same role, but there are minor differences:



In Akili, the attrs property, like scope, is Proxy, that is, you can add, change, or delete the attribute of the html element by doing the appropriate operations with some property of this object. The properties of the attrs object are synchronized with the attributes of the element.



You can use attributes for binding. In the example above, if the scope variable this.example of the MyComponent component changes, the changed method of the NineComponent will be called. Please note we have not done anything special for this. The expression in the str attribute is no different from the examples at the beginning, where we simply displayed the value in the template.



For convenience, you can use the abbreviated version of changed .



 class NineComponent extends Akili.Component { changed(key, value) { if(key == 'str') { this.addNine(value); } } } 


 class NineComponent extends Akili.Component { changedStr(value) { this.addNine(value); } } 


The examples above are equivalent. In order not to produce a mountain of iphs or cases, it is easier to write the right method right away. The principle of naming is simple: changed + the name of the attribute camel case with a capital letter .



Developments



Everything is simple here, add a dash after on , and then everything is as usual. Let's change our initial example:



 class MyComponent extends Akili.Component { static events = ['timeout']; constructor(...args) { super(...args); this.scope.example = 'HelloWorld'; this.scope.sayGoodbye = this.sayGoodbye; } compiled() { setTimeout(() => this.attrs.onTimeout.trigger(9), 5000); } sayGoodbye(event) { console.log(event instanceof Event); // true this.scope.example = 'Goodbye World'; } } 


 <body> <my-component on-timeout="${ console.log(event.detail); // 9 }"> <button on-click="${ this.sayGoodbye(event) }">say goodbye</button> ${ this.example } </my-component> </body> 


The event system is based on the native. In the example above, you can see that you can also create and trigger your custom events.



Work with arrays



 class MyComponent extends Akili.Component { constructor(...args) { super(...args); this.scope.data = []; for (let i = 1; i <= 10; i++) { this.scope.data.push({ title: 'value' + i }); } } } 


 <my-component> <for in="${ this.data }"> <loop>${ this.loopIndex } => ${ this.loopKey} => ${ this.loopValue.title }</loop> </for> </my-component> 


 <my-component> <ul in="${ this.data }"> <li>${ this.loopValue }</li> </ul> </my-component> 




Additionally



Out of the box, Akili also has a router, a library for making ajax requests, a lot of system components for working with cycles, forms, the ability to tie server rendering, etc., you can find a detailed description in the documentation .



This article is written to introduce you to Akili, I tried to reveal some technical aspects in general, but even one fifth of what the framework contains contains does not fit. Much more information is in the documentation, and if there is interest, I will begin to disclose the subject more deeply in other articles.



Framework while in beta, try, watch)

Source: https://habr.com/ru/post/331704/



All Articles