
Akili — javascript фреймворк, который появился под влиянием таких решений как React, Angular, Aurelia и в меньшей степени некоторых других. Целью было объединить все лучшее, что я вижу в них и максимально все упростить.
Нравится React, но отталкивает JSX? Любите Angular, но надоела всякая магия?
Тогда вам стоит попробовать это.
Я убежден, что наилучший способ в чем-то разобраться это практика. Поэтому начну описание сразу же с примеров. Они написаны так, как если бы мы компилировали код с помощью Babel (es2015 + stage-0).
Первые шаги
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>
Здесь мы создали свой первый компонент, зарегистрировали его и инициализировали приложение. Обычный компонентный подход на первый взгляд, но сразу хотел бы отметить пару моментов.
Во-первых, область видимости компонента разделена от области видимости разметки. То есть, можно спокойно наследовать компоненты и это никак не отразиться на этой самой разметке.
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>
За область видимости разметки отвечает свойство компонента scope. Это специальный объект, который вы можете заполнить необходимыми данными и отображать их в шаблонах с помощью выражений вида
${ this.example }, где this и есть этот самый scope. На самом деле в скобках может быть любое javascript выражение.Во-вторых, области видимости разметки также наследуются. Добавим в scope первого компонента новое значение:
class MyComponent extends Akili.Component { constructor(el, scope) { super(el, scope); scope.example = 'Hello World'; scope.test = 'Test'; } }
Тогда разметка ниже:
<body> <my-component> <b>${ this.example }</b> <my-second-component>${ this.example } - ${ this.test }</my-second-component> </my-component> </body>
После компиляции будет выглядеть как:
<body> <my-component> <b>Hello World</b> <my-second-component>Goodbye World - Test</my-second-component> </my-component> </body>
В-третьих, синхронизация логики компонента с его шаблоном происходит путем лишь изменения переменной scope в любой момент времени.
class MyComponent extends Akili.Component { constructor(...args) { super(...args); this.scope.example = 'Hello World'; setTimeout(() => { this.scope.example = 'Goodbye World'; }, 1000); } }
Через секунду значение переменной изменится и в объекте и в шаблоне.
Lifecycle в двух словах, в сравнении с React
.constructor(el, scope)
Прежде всего, поскольку любой компонент это простой javascript класс, будет вызван конструктор. Он получает в аргументы html элемент, к которому будет привязан компонент и объект scope. Здесь вы можете делать с элементом любые изменения, либо отменить компиляцию, в случаи необходимости, вызовом метода .cancel().
.created()
Если компиляция компонента не была отменена, то вы попадаете сюда. Этот метод фактически ничем не отличается от конструктора. В React, похожую функцию выполняет componentWillMount.
.compiled()
Здесь компонент скомпилирован, в шаблонах вместо выражений уже соответствующие значения.
В React это componentDidMount. Вы имеете доступ ко всем родительским элементам, которые к этому моменту гарантированно скомпилированы тоже.
.resolved()
Этот метод, насколько я знаю, не имеет аналогов в известных мне фреймфорках.
Дело в том, что Akili позволяет использовать при компиляции асинхронные операции, если это нужно. К ним относятся некоторые системные и любые кастомные операции. Например, загрузка шаблона компонента из файла:
class MyComponent extends Akili.Component { static templateUrl = '/my-component.html'; constructor(...args) { super(...args); this.scope.example = 'Hello World'; } }
Или любая асинхронная операция, которую мы выполним сами:
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)); } }
В методе compiled вы можете вернуть промис, тогда resolved будет ждать выполнения всех асинхронных операций. При этом сама компиляции будет происходить синхронно.
Другими словами в методе resolved вы можете быть уверены, что скомпилированы абсолютно все дочерние элементы, любого уровня вложенности, в том, числе содержащие какие-либо асинхронные операции.
.removed()
Вызывается при удалении компонента. Аналог — componentDidUnmount.
Универсальность, изоляция, модульность компонентов
Очень важно, чтобы компонент мог быть полностью изолирован и вообще не зависел от внешних условий. Вот пример такого компонента:
import Akili from 'akili'; class NineComponent extends Akili.Component { static template = '${ this.str }'; static define() { Akili.component('nine', NineComponent); } created( { this.scope.str = ''; } compiled() { this.attr('str', val => this.addNine(val)); } addNine(value) { this.scope.str = value + '9'; } }
Добавим его к предыдущим примерам:
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>
Итак, вот что мы получим после компиляции:
<body> <my-component> <nine str="Hello World">Hello World9</nine> </my-component> </body>
Обратите внимание, NineComponent получился абсолютно обособленным. Он похож на функцию, которая может принимать какие-то аргументы и что-то с ними делать. В данном случаи просто добавляет цифру 9 в конец переданной строки и отображает ее.
Можно провести аналогию между атрибутами в Akili и свойствами в React.
this.attrs => this.props. Они выполняют одну и туже роль, но есть мелкие различия: В Akili свойство attrs как и scope является Proxy, то есть можно добавить, изменить или удалить атрибут html элемента, делая соответствующие операции с каким-то свойством данного объекта. Свойства объекта attrs синхронизируются с атрибутами элемента.
Вы можете использовать атрибуты для биндинга. В примере выше, если переменная области видимости this.example компонента MyComponent изменится, то будет вызвана функции, переданная в метод attr у NineComponent. Обратите внимание, мы не сделали для этого ничего особенного. Выражение в атрибуте str ничем не отличается от примеров в начале, где мы просто отображали значение в шаблоне.
События
Здесь все просто, добавляем тире после on, а дальше все как обычно. Изменим наш первоначальный пример:
class MyComponent extends Akili.Component { static events = ['timeout']; created { 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>
Система событий основана на нативной. В примере выше видно, что вы также можете создавать и вызывать свои кастомные события.
Работа с массивами
class MyComponent extends Akili.Component { created() { 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>
Дополнительно
Из коробки Akili также имеет роутер, библиотечку для совершения ajax запросов, множество системных компонентов для работы с циклами, формами, возможность прикрутить серверный рендеринг и.т.д, в документации вы можете найти подробное описание.
Данная статься написана чтобы познакомить вас с Akili, я постарался раскрыть в целом какие-то технические моменты, но здесь не уместилась даже пятая часть того, что в себе содержит фреймворк. Гораздо больше информации есть в документации, ну и если будет интерес, то начну раскрывать тему глубже в других статьях.
