Реактивность

Отступление 6.5

На данный момент переведено

В этой главе вы:

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

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

    Просто задумайтесь об этом ненадолго: за кулисами Meteor способен изменить любую часть вашего пользовательского интерфейса при обновлении лежащей в его основе коллекции.

    Императивным способом сделать это будет использование .observe(), - функции курсора, которая вызывает коллбеки (callbacks - функции-обработчики события) при изменении соответствующих данному курсору документов. Далее с помощью этих коллбеков можно преобразовать DOM (отрисованный HTML нашей web-страницы). В итоге код будет выглядеть как-то так:

    Posts.find().observe({
      added: function(post) {
        // при исполнении коллбека 'added' добавляем HTML элемент
        $('ul').append('<li id="' + post._id + '">' + post.title + '</li>');
      },
      changed: function(post) {
        // при исполнении коллбека 'changed' преобразуем текст HTML элемента
        $('ul li#' + post._id).text(post.title);
      },
      removed: function(post) {
        // при исполнении коллбека 'removed' удаляем HTML элемент
        $('ul li#' + post._id).remove();
      }
    });
    

    Вы, возможно, уже поняли, что такой код очень быстро станет запутанным. Представьте, что вам нужно будет иметь дело с изменениями для каждого атрибута поста и заменять сложный HTML внутри соответствующего <li> элемента. Не говоря уже обо всех замысловатых крайних случаях, которые могут возникнуть, когда мы начнем полагаться на разные источники информации, способные изменяться в реальном времени.

    Когда нам следует использовать observe()?

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

    В таких случаях нужно будет использовать коллбеки observe(), чтобы карта “разговаривала” с Meteor коллекцией и знала, как реагировать на изменения данных. К примеру, вы будете полагаться на коллбеки added и removed для вызова собственных методов API карты dropPin() или removePin().

    Декларативный подход

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

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

    А если покороче, то вместо того, чтобы думать о коллбеках observe, благодаря Meteor мы можем писать:

    <template name="postsList">
      <ul>
        {{#each posts}}
          <li>{{title}}</li>
        {{/each}}
      </ul>
    </template>
    

    Затем получаем список постов, используя:

    Template.postsList.helpers({
      posts: function() {
        return Posts.find();
      }
    });
    

    За кулисами Meteor настраивает коллбеки observe() за нас и перерисовывает соответствующие секции HTML при изменении реактивных данных.

    Отслеживание зависимостей в Meteor: вычисления

    Хотя Meteor является реактивным фреймворком, работающим в реальном времени, не весь код внутри приложения реактивный. Если бы это было так, то все приложение перезапускалось бы каждый раз, когда что-нибудь изменяется. Вместо этого реактивность ограничена отдельными областями кода, которые называют вычислениями(computations).

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

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

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

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

    Устанавливаем вычисление

    Теперь когда мы понимаем теорию, которая стоит за вычислениями, было бы неплохо создать одно из них. Мы можем использовать функцию Tracker.autorun, чтобы заключить блок кода внутри вычисления и сделать его реактивным:

    Meteor.startup(function() {
      Tracker.autorun(function() {
        console.log('There are ' + Posts.find().count() + ' posts');
      });
    });
    

    Обратите внимание, что нам нужно поместить блок Tracker внутри Meteor.startup(), чтобы убедиться, что он исполняется только после того, как Meteor закончил загружать коллекцию Posts.

    За кулисами autorun затем создает вычисление и настраивает его, чтобы пересматривать содержимое при изменении источников данных, от которых он зависит. Мы установили простое вычисление, которое печатает в консоль количество постов. Так как Posts.find() - реактивный источник данных, он позаботится о том, чтобы сообщать вычислению о необходимости пересмотра содержимого каждый раз, когда количество постов изменяется.

    > Posts.insert({title: 'New Post'});
    There are 4 posts.
    

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