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.

.changed(key, value)
Вызывается при изменении любого атрибута компонента. Аналог — componentWillReceiveProps.
Это очень важная часть фреймфорка, поэтому опишу ее более подробно в отдельной секции ниже.

Универсальность, изоляция, модульность компонентов


Очень важно, чтобы компонент мог быть полностью изолирован и вообще не зависел от внешних условий. Вот пример такого компонента:

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';
  }
}

Добавим его к предыдущим примерам:

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 изменится, то будет вызван метод changed у NineComponent. Обратите внимание, мы не сделали для этого ничего особенного. Выражение в атрибуте str ничем не отличается от примеров в начале, где мы просто отображали значение в шаблоне.

Для удобства можно использовать сокращенную версию 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);
  }
}

Примеры выше эквиваленты. Чтобы не плодить гору ифов или кэйсов, проще писать сразу нужный метод. Принцип именования прост: changed + название атрибута кэмел кейсом с заглавной буквы.

События


Здесь все просто, добавляем тире после on, а дальше все как обычно. Изменим наш первоначальный пример:

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>

Система событий основана на нативной. В примере выше видно, что вы также можете создавать и вызывать свои кастомные события.

Работа с массивами


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>


Дополнительно


Из коробки Akili также имеет роутер, библиотечку для совершения ajax запросов, множество системных компонентов для работы с циклами, формами, возможность прикрутить серверный рендеринг и.т.д, в документации вы можете найти подробное описание.

Данная статься написана чтобы познакомить вас с Akili, я постарался раскрыть в целом какие-то технические моменты, но здесь не уместилась даже пятая часть того, что в себе содержит фреймворк. Гораздо больше информации есть в документации, ну и если будет интерес, то начну раскрывать тему глубже в других статьях.

Фреймворк пока в бете, пробуйте, смотрите )
Поделиться с друзьями
-->

Комментарии (67)


  1. serf
    26.06.2017 20:11

    class MySecondComponent extends MyComponent  {
     constructor(...args) {
        super(...args);
        this.scope.example = 'Goodbye World';
      }
      myOwnMethod() {}
    }

    Как в этом случае добавляются/описываются дополнительные зависимости для MySecondComponent?


    Во-вторых, области видимости разметки также наследуются.

    Прототайпинг скоупа как раз ангуляровская фишка (точнее обычный JS прототайпинг), от которой лучше отказаться, так же лучше отказаться от любых вещей завязанных на специфику фреймворка, передавать все явном виде через атрибуты. Компоненты по-моему мнению компоненты должны быть независимыми/изолированными.


    1. IsOrtex
      26.06.2017 20:30

      1) Не совсем понял, что имеется ввиду под дополнительными зависимостями?
      2) Тут согласен, но это скорее просто бонус, разные ситуации бывают. Про то как сделать компоненты модульными я в статье описал.


      1. alexiusp
        27.06.2017 06:34

        имеется в виду ангуляровское внедрение зависимостей — Dependency Injection


        1. serf
          27.06.2017 09:54

          Верно, вот пример возможной реализации https://habrahabr.ru/company/ncloudtech/blog/321584/#comment_10243970


  1. teslitsky
    26.06.2017 20:31
    +6

    Если хочется React + Angular не лучше ли взять Vue.js?


    1. PYXRU
      26.06.2017 20:38
      +4

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

      Синтаксис так себе, интуитивно не понятный, по сути скрестили кота с ежом без без поддержки кота и ежа. И вызовы конструктора базового класса, тоже не удобно, нужны обертки. Сверху правы берите Vue и не мучайтесь


      1. IsOrtex
        26.06.2017 20:50

        Весь синтаксис это доллар и фигурные скобки в стиле es6, причем во всех кейсах )


        1. staticlab
          27.06.2017 07:17

          Доллар и фигурные скобки в стиле ES6, а стрелки — не в стиле ES6. Ну и зачем так путать?


          1. IsOrtex
            27.06.2017 09:05

            Неудачный пример получился наверное, согласен ) Стрелки просто часть html


            1. staticlab
              27.06.2017 09:09

              С каких пор стрелки — часть HTML?


              1. mayorovp
                27.06.2017 09:15

                Имелось в виду, что стрелки — часть выводимого HTML, они будут отображаться на странице.


                1. IsOrtex
                  27.06.2017 09:22

                  да, именно )


    1. Kroid
      26.06.2017 21:25
      +5

      Именно! Vue — это то, каким должен был быть ангуляр.


      1. AnneSmith
        06.07.2017 13:28
        -1

        все популярные фреймворки суть одно и то же: крайне не организованный код и hardcoded html, которые невозможно использовать в другом проекте

        графический интерфейс очень прост и имеет простые стандартные элементы и функционал, поэтому кодирование бизнес логики не должно быть таким сложным


  1. unsafePtr
    26.06.2017 22:08
    -2

    Увидел stage-0 перестал читать дальше. Если stage-0 пускать в продакш, то так далеко не уехать.


    1. justboris
      27.06.2017 02:23

      Подозреваю, что автор просто не в курсе, что class fields уже давно не stage-0, а 2. Стадии показываются тут. Stage-2 будет достаточно, чтобы код из статьи собрался.


      Да и вообще, набор es2015+stage-0 это очень странно. То есть мы берем 2015, пропускаем 2016, 2017 (которые уже тоже часть стандарта и есть в некоторых браузерах), зато берем всякую дичь из stage-0.


      Сам Babel рекомендует использовать babel-preset-env, который будет обновляться по мере добавления новых фич в стандарт, и также будут выпиливаться преобразования для штук, которые уже нативно есть в браузерах (список поддерживаемых браузеров настраивается)


      1. IsOrtex
        27.06.2017 06:54

        Можно собрать как удобно, просто хотел предупредить о свойствах класса в описании.


        1. justboris
          27.06.2017 15:31
          -1

          Что-то я не стал бы доверять фреймворку, разработчик которого говорит "возьмите какие-нибудь настройки Babel", вместо минимально необходимого набора плагинов.


          1. IsOrtex
            27.06.2017 16:21

            Суть фреймворка-то не в том как скомпилировать какое-то конкретное приложение, можно хоть в head его подключить и все будет работать.


            1. napa3um
              27.06.2017 16:29

              Потому что это библиотека, а не фреймворк :).


  1. Akuma
    26.06.2017 22:21

    Сайт лежит в 502-й. Видно не ожидали вы местного наплыва :)

    По теме: первое впечатление — Vue.js.
    Но, честно говоря, мне больше нравится именно React на который я пересел с Ангуляра (первого). В особенности нравится JSX за его 100% совместимость с JS. А все эти прилепленные директивы и циклы через html жутко неудобны, как по мне.

    В статье вы сначала пишите, что скрестили Ежа с Коноплей, но потом эта мысль как-то сразу теряется. В чем преимущества перед обоими фреймворками? Их потому и два, что они офигеть какие разные. И не нужно это менять.

    Кстати, везде в конструкторе присутствет super(...args);
    Нужно этого как-то избегать, либо добавить аналог componentDidMount(), а то забыл поставить — и что будет?


    1. IsOrtex
      27.06.2017 06:38

      сайт лежал — обнаружил memory leak в jsdom, буду искать решение.

      аналог componentWillMount — created
      аналог componentDidMount — compiled

      То есть конструктор можно вообще и не писать.


  1. justboris
    27.06.2017 02:05

    Про шаблоны совсем ничего не написано. Как написать такой компонент, который из этого html:


    <hello-component name="Tester" description="test user" />

    сделает вот такой:


    <div>
      <h1>Hello, tester!<h1>
      <p>test user</p>
    </div>

    ?


    1. IsOrtex
      27.06.2017 06:40

      class HelloComponent extends Akili.Component {
       static templateUrl = './path/to/template';
      }
      


      или

      class HelloComponent extends Akili.Component {
        static template = `<div>
          <h1>Hello, tester!<h1>
          <p>test user</p>
        </div>`;
      }
      


      1. justboris
        27.06.2017 10:26

        А что будет, если внутри HelloComponent будет разметка:


        <hello-component>Текст внутри</hello-component>

        Шаблон ее перезапишет или добавится в конец?


        1. IsOrtex
          27.06.2017 13:53

          перезапишет, но если содержимое нужно куда-то вставить, то можно использовать свойство скоупа .__content

          class HelloComponent extends Akili.Component {
            static template = `<div>
              <h1>Hello, tester!<h1>
              <p>${ this.__content }</p>
            </div>`;
          }
          


          В приме выше выражение ${ this.__content } заменится на Текст внутри


  1. justboris
    27.06.2017 02:13

    .resolved() Этот метод, насколько я знаю, не имеет аналогов в известных мне фреймфорках.

    Не имеет аналогов, не потому что их разработчики не догадались об этом, а потому, что надо что-то показывать, пока компонет загружает свои зависимости (спиннер, например).


    В React это делается тривиально


    class ComponentWithLoader extends React.Component {
      state = { loading: false, data: null };
    
      componentDidMount() {
         this.setState({loading: true})
         loadData().then((data) => this.setState({loading: false, data: data}));
      }
    
      render() {
        if(this.state.loading) {
           return <Loader />
        }
        return <div>{/*какой-то контент*/}</div>
      }
    }

    При желании можно завернуть в переиспользуемый компонент, или взять уже готовый.
    Тащить это в ядро фреймворка смысла нет.


    1. IsOrtex
      27.06.2017 06:47

      Смысл все же есть. Бывают, например, группы компонентов, когда их несколько и они завязаны друг на друге. Например посмотрите реализацию табов.

      Родительский компонент может захотеть что-то сделать с дочерними. Метод resolved гарантирует, что в нем все дочерние будут полностью скомпилированы и тогда к ним можно обратиться через систему коммуникации компонентов.


      1. justboris
        27.06.2017 10:29

        Посмотрел на реализацию табов, понятнее не стало. В исходном коде нет ни одной асинхронной операции, откуда там resolved нужен, совсем не ясно


        1. IsOrtex
          27.06.2017 15:49

          Дело в том что компиляция идет сверху вниз по дереву, элемент за элементом, поэтому если нужно что-то сделать с дочерними элементами из родительского после его компиляции, то мы также используем resolved. В нем все дочерние компоненты будут гарантировано скомпилированы, в этом его смысл.

          В целом, метод resolved используем когда нужен доступ к детям.

          Такой лайфцикл позволяет работать с компонентами, почти так как это происходит в DOM:
          В примерах ниже левая и правая части эквивалетны:

          el.querySelector('input') => component.child('input')
          el.querySelectorAll('input') => component.children('input')
          el.parentNode => component.parent()

          Разница только в том, что все методы справа возвращают не элемент, а компонент.
          В компонентах Akili есть много разных методов для взаимодействия друг с другом

          class ParentComponent extends Akili.Component {
            resolved() {
               let childComponent = this.child('child');
          
               console.log(childComponent.scope.example); //  'Hello World'
          
               childComponent.scope.example = 'Goodbye World'; // в разметке значение также поменяется
            }
          }
          
          class ChildComponent extends Akili.Component {
            compiled() {
              scope.example = 'Hello World';
            }
          }
          
          Akili.component('parent', ParentComponent); 
          Akili.component('child', ChildComponent); 
          

          <parent>
             <child>${ this.example }</child>
          </parent>
          



          1. mayorovp
            27.06.2017 15:52

            childComponent.scope.example = 'Goodbye World'; — про инкапсуляцию, видимо, никто не слышал… Ну нельзя же такие вещи приводить в качестве примера!


            1. IsOrtex
              27.06.2017 16:18

              А в чем тут проблема? Компонент это по сути экземпляр класса, а scope это public property.


              1. mayorovp
                27.06.2017 16:58

                А с какого перепугу scope является public property?


                1. raveclassic
                  27.06.2017 17:11
                  +2

                  Г — гибко


              1. lega
                27.06.2017 17:18
                +1

                По хорошему, компонент должен быть «черным ящиком с парой кнопок» с мнимальным интерфейсом, что-б никто не мог внутрях ковырятся, иначе это будет повышать риск хаоса.


    1. vintage
      27.06.2017 08:38
      -2

      А можно не заниматься ерундой с ручным управлением индикаторами ожидания, воспользовавшись реактивным программированием.


      Ваш, код мог бы выглядеть примерно так:


      class ComponentWithLoader extends MyComponent {
      
        @ $mol_mem()
        render() {
          return $mol_http.request( '/data.json' ).json().map( item => <Item item={ item } /> )
        }
      
      }


      1. justboris
        27.06.2017 10:29
        +1

        Так а что будет видеть пользователь, пока data.json загружается?


        1. vintage
          27.06.2017 14:02
          -1

          Индикатор ожидания, очевидно. Не нравится стандартный, всегда можно запилить свой:


          class ComponentWithLoader extends MyComponent {
          
            @ $mol_mem()
            render() {
              try {
                return $mol_http.request( '/data.json' ).json().map( item => <Item item={ item } /> )
              } catch( error ) {
                return <StatusIndicator error={ error } />
              }
            }
          
          }


          1. mayorovp
            27.06.2017 14:18

            А каким образом неперехваченное исключение превращается в индикатор ожидания?


            1. vintage
              27.06.2017 14:45

              Точно таким же try-catch, но там, где вызывается render (это не в реакте).


          1. justboris
            27.06.2017 15:29

            Что-то это еще больше все запутывает


            catch блок — это про ошибку после загрузки. А в процессе что будет показываться?


            1. mayorovp
              27.06.2017 15:37

              У него в фреймворке состояние "в процессе" считается исключительным. Метод render вычисляется два раза, первый раз отваливается с исключением, а второй раз запускается автоматически когда запрос выполнится. В этот раз метод request не будет делать нового запроса к серверу, а вернет значение, полученное в прошлый раз.


              По-своему красивая идея — жаль только метод request делает не то как называется.


              1. justboris
                27.06.2017 15:40

                Метод с аннотацией mol_mem (мемоизация, как я предполагал), вызывается несколько раз и выдает разный результат?


                Извините, но мне с таким подходом точно не по пути.


                1. mayorovp
                  27.06.2017 15:42

                  Ну, по задумке, это мемоизация с автоматической инвалидацией и уведомлениях об изменении. Хотя я тоже считаю, что стоило бы назвать его как-то по-другому.


                  В Knockout такая штука называется pureComputed, в MobX — computed.


  1. vtvz_ru
    27.06.2017 06:35
    -3

    Как по мне, jsx шикарен, а сам react невероятно элегантен. Нет ни одной задачи, которой я не смог бы решить с его помощью или с помощью библиотеки, которую можно к нему прикрутить.


    В любом случае, библиотека хорошая, "но использовать я ее, конечно, не буду". По крайней мере к реакту у меня доверия всяко больше. Как минимум, ответов на stack overflow больше.


    Желаю дальнейших успехов в разработке)


  1. alexandzolotarev
    27.06.2017 06:35
    -1

    Это всё конечно прекрасно, только вот не совсем понятно зачем всё это. Кто хотел получить Angular + React, получили Vue, иногда мне кажется, что она только для этого и придумана была. Но это, очередной проходной фреймворк.


  1. napa3um
    27.06.2017 07:36

    У вас получился Polymer. Пока чуть менее сложный, но это вы ещё до dom-repeat, обработки массивов, атомарных изменений нескольких полей и оптимизации вычисляемых байндингов не дошли. Ну и без Shadow DOM, HTML-imports и изоляции CSS у вас реализация. А так — один в один «стиль» «фреймворка» (правильнее — библиотеки). Polymer себя позиционирует таки компонентной библиотекой, и вам следует тоже, ибо «простота» и «отсутствие магии» — это и есть отсутствие навязываемой жёсткой структуры проекта, предлагается лишь способ «нарезки» кода на компоненты, а архитектуру из этих компонентов программисту придётся сочинять самостоятельно. Ну и ценность таких библиотек зависит от их заполненности «подходящими» друг к другу как пазлинки готовыми базовыми компонентами, и тут Polymer вас тоже сильно опережает, простой чат (при готовом бэке) можно собрать вообще без кода, только путём связывания атрибутов готовых компонентов в разметке друг с другом (там есть «невизуальные» компоненты типа компонента обеспечивающего роутинг или AJAX-запросы к серверу).

    Призываю присоединиться к Polymer, работы там ещё куча :).


    1. napa3um
      27.06.2017 07:40

      (Прошу прощения, dom-repeat и массивы у вас поддерживаются, роутинг и аякс-запросы тоже в готовых компонентах предложены, поторопился комментировать — теперь я уверен на 100%, что вы вдохновлялись Полимером :))


  1. orcy
    27.06.2017 07:53
    -1

    > Что будет если скрестить React и Angular?

    Reangular? Anact?


    1. IsOrtex
      27.06.2017 09:07

      Regular =) Была идея так назвать… )


  1. mayorovp
    27.06.2017 09:14

    Что-то, как мне кажется, тут была нарушена сама суть понятия "компонент". Компонентом применительно к UI обычно называют обособленную часть интерфейса, обладающую своим [внешним] видом, состоянием и поведением.


    Здесь же "шаблонная" часть компонента вытащена наружу, компоненту остались только состояние и поведение. Это уже не компонент получается, а контроллер из старого AngularJS.


    1. napa3um
      27.06.2017 09:30

      Компонент = логика + шаблон + имя. По имени один компонент может использовать в своём шаблоне другие компоненты. Всё нормально тут с компонентностью, вроде. (Упомяну опять «свой любимый» Polymer, в нём благодаря HTML-imports действительно более «железобетонно» «нарезаются» компоненты, HTML является контейнером и для разметки, и для скриптов, и для стилей, которые, конечно, можно разнести и на отдельные файлы, оставив в HTML ссылки на них. Плюс ко всему этому получается проще сборка — tree-shaking становится действительно tree — сборщик-«вулканизатор» напрямую из построенного импортами DOM-дерева получает всю иерархию зависимостей без необходимости анализировать JS-код.)


      1. mayorovp
        27.06.2017 09:55

        Компонент = логика + шаблон + имя.

        Покажите мне шаблон у вот этого компонента:


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


        1. napa3um
          27.06.2017 10:09
          +1

          Ничего страшного, если шаблон является опциональным аспектом компонента (для организации чего-нибудь типа HOC из Реакта или Behaviors из Полимера, например :)). Компонент не обязан быть визуальным.


  1. i360u
    27.06.2017 10:20
    +2

    Для решения подобных задач, навскидку, на ум приходят варианты сделать это с помощью нативных кастомных элементов или Polymer 2 или Vue.js ...etc. Что я должен знать о Akili, чтобы рассматривать его в качестве одной из альтернатив? В представленном описании я ничего такого не увидел, что я упускаю?


  1. raveclassic
    27.06.2017 10:46
    +3

    • C react сравнили, с angular тоже, а по что aurelia обидели?
    • Какие проблемы вы решали и решили?
    • Какие будут аргументы против фразы "yet another framework"?
    • Что с перфомансом, где бенчи?
    • Ну и, наконец, где работающий туду-лист?


    1. lega
      27.06.2017 11:06

      Примитивный todo есть на главной странице, хотя довольно многословен.


  1. lega
    27.06.2017 10:46

    Через секунду значение переменной изменится и в объекте и в шаблоне.
    Как отслеживается изменения?

    Работают и отслеживаются ли «js» выражения в шаблоне?, наподобии
    ${a+b + ' ' + foo(c) + bar()}


    Зачем нужен scope если есть this? особенно если оно «смешивается» в шаблоне.

        <for in="${ this.data }">
    

    Можно ли сделать вложенный цикл и использовать данные родительского цикла во внутреннем?


    1. IsOrtex
      27.06.2017 11:44
      +1

      Как отслеживается изменения?

      Изменения отслеживаются с помощью механизма Proxy.

      Работают и отслеживаются ли «js» выражения в шаблоне?

      Да, отслеживаются все выражения где есть хоть какая-то переменная скоупа

      Зачем нужен scope если есть this?

      this в html это и есть scope. Поскольку скоуп был придуман как область видимости разметки, то в ней он является контекстом. В компоненте же есть ссылка на на него, в свойстве scope.

      Можно ли сделать вложенный цикл и использовать данные родительского цикла во внутреннем?

      Да, каждый скоуп имеет свойство .__parent — ссылку на родительский

      Также, подробное описание всех возможностей, включая некоторые тонкости, можно найти в документации.


      1. lega
        27.06.2017 14:36
        +1

        Изменения отслеживаются с помощью механизма Proxy.
        ok, оно есть в 72% браузеров, в IE вероятно не появится никогда (в edge оно есть).

        this в html это и есть scope.
        Выглядит как лишняя сущность, было бы красивее разместить данные в this он бы и был this в html.

        Да, отслеживаются все выражения где есть хоть какая-то переменная скоупа
        Даже не так — если меняется любая отслеживаемая переменная, то вы делаете полный dirty-checking — проверяете все отслеживаемые переменные. При этом если данные в scope меняются, которые выводятся через функцию ${this.fullName()} — то обновления не происходит, недоработка.

        Итого: Идет проверка всего на каждое изменение — повышенная нагрузка, хотя Proxy позволяет реагировать точечно, с другой стороны эта «проверка всего» пропускает некоторые типы биндингов в html — хотя у vue.js такая же проблемма, видимо не сильно напрягает, и третье Proxy не поддерживается больщой долей браузеров, как результат — рановато для боя.

        Но для старта неплохо, все фреймворки с чего-то начинали.

        Кстати, если при вызове выражения биндингов будете передавать this как параметр вместо подмены this, то они будут работать на прядок быстрее.


        1. IsOrtex
          27.06.2017 16:02

          Выглядит как лишняя сущность, было бы красивее разместить данные в this он бы и был this в html.

          Не совсем, в выражениях может быть что угодно, хоть ${ console.log('Hello') } хоть ${ window.location.href }
          Область видимости по умолчанию весь window. Если нужно изменить это поведение, то достаточно дописать метод Component.parse как вам надо.
          Даже не так — если меняется любая отслеживаемая переменная, то вы делаете полный dirty-checking — проверяете все отслеживаемые переменные. При этом если данные в scope меняются, которые выводятся через функцию ${this.fullName()} — то обновления не происходит, недоработка.

          Данные которые будут в функции тоже начнут отслеживаются. Слежка происходит не на уровне парсинга этой строки. Тут все гораздо хитрее.

          И еще момент, хоть отслеживается и каждое свойство скоупа отдельно, но синхронизация с шаблоном происходит группами. То есть если пройтись циклом и изменить 1000 значений в скоупе, то произойдет всего 1 операция изменения разметки, а не 1000.


          1. lega
            27.06.2017 17:12
            +1

            Данные которые будут в функции тоже начнут отслеживаются. Слежка происходит не на уровне парсинга этой строки. Тут все гораздо хитрее.
            Что-то не работает: http://jsfiddle.net/lega911/kw3f2mx2/ — fullName не обновляется.

            Странно, в другом примере у меня было полное сканирование, в этом нет.
            Область видимости по умолчанию весь window.
            Это не желательно, т.к. в шаблон может попасть всякая бяка из вне.


            1. IsOrtex
              27.06.2017 18:50
              -1

              Да, по поводу биндинга функции в шаблоне вы правы. Я в своем время долго решал какой вариант оставить. Почему-то запомнилось как будто оставил вариант с учетом всех вызовов.

              Сейчас прикинул все варианты и вернулся к тому же. Выполнять функцию снова все-таки лучше при изменении аргументов, а не содержимого тела.


            1. IsOrtex
              27.06.2017 19:36

              Хотя я все равно не уверен, возможно добавлю такую фичу.


            1. IsOrtex
              27.06.2017 20:23

              Спасибо за концентрацию внимания на этом моменте, я добавил возможность биндинга функции в шаблоне и по изменению аргументов и по телу. Теперь ваш пример должен работать.


  1. XeL077
    27.06.2017 13:36
    -1

    Посмотрел примеры, вы создали странный vue 2.

    Это было написано просто для веселья?
    Возможно, Вам стоит пересмотреть парадигму разработки, придумать что-то новое, мне это кажется интереснее, чем соединять готовые фреймворки.

    image


  1. ju5tify
    27.06.2017 14:06

    Нравится React, но отталкивает JSX? Любите Angular, но надоела всякая магия?

    Vue.js вам в помощь.

    Кстати, в Ангуляре (который .io), как по мне, минимум магии. В отличии от оверхайпнутого Реакта.