Предлагаю вашему вниманию перевод статьи Ember.js ?— ?Goodbye MVC (Part 1).

На конференции EmberConf 2015 Йегуда Кац и Том Дейл сообщили о скором появлении некоторых изменений в Ember 2. В частности, наибольшее внимание привлекли маршрутизируемые компоненты. Они позволяют признать контроллеры устаревшими и убрать их. Конечно, это встревожило многих пользователей Ember, так как Ember и Sproutcore всегда были клиентскими фреймворками MVC.

Прощай MVC


Не секрет, что многие новые соглашения и изменения Ember 2 возникают под непосредственным влиянием React и Flux (паттерн для потока данных в приложениях React). То, что во Flux называют «однонаправленным потоком данных», в Ember является паттерном «данные вниз, действия вверх» (DDAU).

Вместо традиционного паттерна клиентского MVC DDAU предлагает один поток данных (отсюда и направленность в одну сторону), что позволяет проще воспринимать код приложения и улучшать производительность. Фундаментальная проблема с MVC раскрывается по мере роста и усложнения приложения. Каскадное обновление и неочевидные зависимости ведут к неразберихе и путанице. Обновление одного объекта приводит к изменению другого, что, в свою очередь, запускает следующие процессы и в конечном счете превращает поддержку приложения в настоящий «кошмар».

imageEmber 2.0 Data Down, Actions UP

В паттерне DDAU потоки данных однонаправленные, и здесь нет двухсторонних привязок. Разные части приложения могут оставаться в значительной мере несвязными и понятными. Это значит, что вы всегда будете знать источник изменения объекта. Если вы читали мой пост на тему функционального программирования и эффекта наблюдателя, то вы поймете, почему так важно сохранить независимость компонентов от сторонних эффектов.

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

Подготовка приложения для DDAU


Когда появятся маршрутизируемые компоненты, контроллеры устареют и будут убраны из фреймворка. Контроллеры и представления всегда путали новых пользователей Ember, и «в 80 % случаев применения они исполняли роль компонента» (в этом видео от Иегуды и Тома вы сможете узнать больше).

Так как компоненты не являются синглтонами, когда Glimmer посчитает нужным, они будут разобраны и отображены заново в оптимизированной форме. Но если контроллеры уберут, как справиться с сохранением состояния?

Например, у вас в контроллере есть некоторое свойство. Оно удерживает состояние, которое вы хотели бы сохранить в приложении. Чтобы сделать это в Ember 2, мы можем удалить это свойство контроллера и поставить вместо него «компоненты с поддержкой сервисов». Сервис будет сохранять состояние одиночного компонента и непосредственно вводить его, только когда это необходимо. Так как сервисы предоставляют много возможностей, некоторые разработчики могут ими злоупотреблять. О сервисах я скажу в конце статьи.

Реализация компонентов с поддержкой сервисов


В следующем примере, я продемонстрирую, как сегодня можно использовать сервисы и однонаправленные привязки. Вы можете читать текст ниже и параллельно использовать это demo.

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

В шаблоне маршрута мы можем просто отобразить введенное состояние сервиса через хелпер each.

{{! animals/index.hbs }}
<div class="row">
  <div class="col-md-3">
    <h2>Select Animals</h2>

    {{checkbox-group
        group=animals
        selectedItems=checkboxGroup.selectedItems
        check=(action "check")
    }}
  </div>

  <div class="col-md-9">
    <h3>Selected Animals</h3>
    <table class="table table-bordered">
      <thead>
        <tr>
          <th>ID</th>
          <th>Species</th>
          <th>Name</th>
        </tr>
      </thead>

      <tbody>
        {{#each checkboxGroup.selectedItems as |animal|}}
          <tr>
            <td>{{animal.id}}</td>
            <td>{{animal.species}}</td>
            <td>{{animal.name}}</td>
          </tr>
        {{/each}}
      </tbody>
    </table>
  </div>
</div>

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

// animals/controller.js
import Ember from 'ember';

const { inject: { service }, Controller } = Ember;
const OPERATION_MAP = {
  true: 'addObject',
  false: 'removeObject'
};

export default Controller.extend({
  checkboxGroup: service(),
  
  // In the future, actions will be defined in the route and passed into the 
  // routable component as `attributes`.
  actions: {
    check(group, item, isChecked) {
      return group[OPERATION_MAP[isChecked]](item);
    }
  }
});

Как упоминалось выше, сам по себе сервис простой. Позднее мы можем определить более сложное поведение, но здесь лежащее в основе сохранение для состояния — это просто массив JavaScript.

// checkbox-group/service.js
import Ember from 'ember';

const { Service } = Ember;

export default Service.extend({
  init() {
    this._super(...arguments);
    this.selectedItems = [];
  }
});

Так как в этом примере мы используем простое поведение, нет необходимости определять подкласс компонента. Действие check передается из маршрутизируемого компонента/контроллера, поэтому использование замкнутых действий в шаблоне компонента означает, что мы не должны приводить sendActions к void.

В нашем шаблоне компонента мы используем небольшие компонуемые хелперы. Эти хелперы, по сути, являются простыми функциями JavaScript. И так как они возвращают значения, мы можем использовать их как подвыражения Handlebars, где мы могли бы один раз определить вычислительное свойство.

Хелпер contains не входит в Ember изначально, но сама функция представляет собой одну строку кода. Есть ряд полезных дополнений, которые добавляют в приложение хелперы. Например, дополнение ember-truth-helpers я сама использую почти в каждом приложении.

{{! checkbox-group/template.hbs }}
{{#each group as |item|}}
  <div class="checkbox">
    <label for={{concat "item-" item.id}}>
      {{one-way-input
          id=(concat "item-" item.id)
          class="checkbox"
          type="checkbox"
          checked=(contains selectedItems item)
          update=(action this.attrs.check selectedItems item)
      }} {{item.name}} <span class="label label-default">{{item.species}}</span>
    </label>
  </div>
{{/each}}

Как упоминалось в моей предыдущей статье, дополнение ember-one-way-input — простой способ уже сейчас использовать односторонние привязки.

Я надеюсь, что этот простой пример наглядно показывает, как вы можете создать удобное в сопровождении и эффективное приложение с помощью некоторых из моих любимых особенностей Ember: хелперов, замкнутых действий, компонентов и односторонних привязок.

Предостережение по поводу сервисов


Здесь все как в известном выражении: «Чем больше сила, тем больше и ответственность». Так как сервисы в Ember — это синглтоны, вас будет искушать желание делать по несколько сервисов и вводить их повсюду.

Если вы создаете сервис только для того, чтобы использовать его как глобальный объект, то в целом код будет «с душком», потому что зависимости станут неочевидными (ранее мы уже выяснили, что это плохо), и части приложения станут тесно связанными. Вместо этого раскрывайте данные и действия через интерфейсы, чтобы сохранить код разделенным и ясным. Помните принцип про силу и ответственность и используйте сервис только при крайней необходимости!

Когда же использовать сервисы?


Мне нравится ответ из Stack Overflow по поводу использования синглтонов. По сути, вам следует использовать их только в том случае, когда у вас может быть только один экземпляр во всем приложении. Например, корзина в интернет-магазине, лента активности или мгновенные сообщения могли бы стать отличными кандидатами для использования сервиса.



Перевод статьи — Ember.js?—?Goodbye MVC (Part 1)

Отдельное спасибо переводчикам и участникам сайта emjs.ru за предоставление статьи.

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


  1. 404
    07.01.2016 16:59
    +7

    Не нужно, всё-таки, переводить дословно singleton и helper. Эти термины давно и успешно транслитерировались.


    1. falkon
      08.01.2016 12:25

      Поправил, спасибо.


  1. Meliborn
    08.01.2016 00:43
    +6

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

    Чего-чего? Я недавно начал изучать Ember (сам пишу на рельсах) и это просто ад для новичка. С каких пор роуты рендерят темлейты и возвращают модели? Нафига вьюхи и где разница между ними, компонентами и темплейтами? А теперь еще контроллеры захотели убрать. Что за каша, зачем так усложнять? Я и так долго думаю в какие части этого эмбера засунуть нужный кусок кода и откуда что доступно. Простые вещи превращаются в часы планирования. Может быть со временем привыкнешь (как и ко всему) и не будешь думать куда что написать, но говорить что эмбер имеет легкий порок вхождения — это эребор.


    1. tenbits
      08.01.2016 14:45

      > С каких пор роуты рендерят темлейты

      Не могли бы вы подробнее остановиться на этом моменте? Это ведь довольно распространенная практика что за путём регистрируется лишь шаблон.


      1. falkon
        08.01.2016 14:53

        По ссылкам ниже все подробно разобрано:
        Отображение шаблона
        Определение маршрутов


        1. tenbits
          08.01.2016 15:01

          Спасибо за ссылки, но что плохого в том что «роуты рендерят темлейты»?


          1. falkon
            08.01.2016 15:19

            Это отличается от MVC, где за это отвечают контроллеры. Многим это не нравится, так как предлагается другой подход, причем контроллеры вообще хотят убрать из Ember заменив на компоненты.
            Подробнее тут — Контроллеры


            1. tenbits
              08.01.2016 15:28

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

              <Users/>
              . Далее вызывается контроллер «Users» и далее по старой, или вашими словами — привычной, схеме. Такое поведения на порядок лучше, чем прямой вызов контроллера «Users», так как шаблон разрешает создавать композицию из контроллеров и много прочих классных штук.


              1. falkon
                08.01.2016 15:54

                так как шаблон разрешает создавать композицию из контроллеров

                Это вы говорите о компонентах, для маршрута может быть только один соответствующий контроллер.


                1. tenbits
                  08.01.2016 17:48

                  Компоненты можно тоже по принципу MVC строить, или одному из его вариаций.

                  > для маршрута может быть только один соответствующий контроллер
                  Это если только жестко привязываться к «Роут-Контроллер» архитектуре, но достаточно ввести между ними шаблон и уже можно комбинировать. И именно такой подход набирает популярность, так как он сочетает в себе и «Роут-Контроллер» вариант, и композицию.


                  1. VasilioRuzanni
                    09.01.2016 12:06

                    И именно такой подход набирает популярность, так как он сочетает в себе и «Роут-Контроллер» вариант, и композицию.

                    Потом уходит само (ненужное здесь, по большому счету) понятие «контроллера» и это просто называется компонентом :)


                    1. tenbits
                      10.01.2016 23:06

                      Ну как же, контроллер и компонент это разные вещи. Компонентом называют законченную единицу интерфейса/приложения — и он как раз состоит из контроллера, или шаблона, или только модели, также может другие ресурсы и компоненты включать в себя. Вообщем компоновать все это вещи можно как угодно. Здесь «компонент» был назван в контексте HMVC.


      1. vintage
        08.01.2016 15:09

        Дурная практика, на самом деле. Состояние в урле — это такое же состояние, как и состояние в локальном хранилище и состояние на сервере. В идеале работа со всеми этими состояниями должна быть единообразной. Более того, зачастую то какую страничку показать зависит сразу от нескольких типов состояний (конфиг пользователя с сервера, урл с переопределениями, локальное хранилище с теми данными, что не хотелось бы держать в урле).


        1. tenbits
          08.01.2016 15:31

          Вовсе нет. Шаблон в данном случае как middleware между роутом и контроллером, смотрите пример выше про Users. А всё вами описанное можно реализовать или «по-старинке» или же через композицию. Последнее намного удобнее.


          1. vintage
            08.01.2016 15:38

            Я не про шаблоны в данном случае, а про роуты. Состояние UI зависит не только и не столько от URL. Яркий пример — позиция проигрывания видео на вытрубе. Сохраняется в локальное хранилище, но может быть перегружена через URL.


            1. tenbits
              08.01.2016 15:46

              Вы меня запутали) Изначально обсуждалась тема, что плохого в том, что «роуты рендерят шаблоны». А если говорить лишь о URL, то разумеется не всегда возможно, или вернее сказать, не всегда целесообразно, хранить состояние приложения в путях.


              1. vintage
                08.01.2016 15:58
                +1

                Я к тому, что роуты — это довольно опциональные штуки и странно на них завязывать ядро фреймворка. Всё же это задача контроллера (пусть и в варианте сервиса или метода компонента) посмотреть в различные состояния и решить что и как рендерить.


  1. vintage
    08.01.2016 00:49
    +1

    В сочетании с паттерном DDAU и механизмом визуализации Glimmer разработчики Ember изначально имеют в своем распоряжении и производительность и эффективность.
    Надеюсь они сотворили там чудо, ибо первая версия в два раза медленнее даже ангуляра: nin-jin.github.io/todomvc/benchmark
    Кто бы написал ToDoMVC на Ember2?


    1. falkon
      08.01.2016 12:32

      В Ember 2 производительность хорошо подтянули.
      Свежее сравнение Ember 1, Ember 2, React и IDOM можно посмотреть в этом обзоре (En).


      1. vintage
        08.01.2016 12:59

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


        1. gagoman
          08.01.2016 13:07

          Есть предложения, как сделать быстрей? Jin быстрее React?


          1. vintage
            08.01.2016 13:35
            +1

            Да, отказаться от виртуального дома в пользу прямого точечного изменения реального. В тесте выше раза в два быстрее, angular light и vue вообще в 3.