Реактивность и односторонняя привязка данных

Riot.js — это легковесный фронтенд-фреймворк, ориентированный на создание компонентного интерфейса с минимальной абстракцией и высокой производительностью. Ключевым принципом работы с Riot.js является реактивность данных и односторонняя привязка, которые обеспечивают согласованное обновление интерфейса без избыточных перерисовок.


Основы реактивности

Реактивность в Riot.js строится на наблюдаемых данных. Любое изменение состояния компонента автоматически приводит к обновлению DOM, где это состояние используется. Для создания реактивного свойства в компоненте используется объект this:

<counter>
  <button oncl ick={decrement}>-</button>
  <span>{ count }</span>
  <button oncl ick={increment}>+</button>

  <script>
    this.count = 0

    this.increment = () => this.count++
    this.decrement = () => this.count--
  </script>
</counter>

В этом примере переменная count связана с отображением <span>{ count }</span>. Любое изменение this.count автоматически обновляет DOM. Riot.js обеспечивает мгновенную синхронизацию состояния и интерфейса без явного вызова методов обновления.


Односторонняя привязка данных

Односторонняя привязка данных означает, что поток данных направлен только от модели к представлению. Изменения интерфейса не меняют состояние напрямую, за исключением специальных обработчиков событий. Это позволяет:

  • предотвращать непредсказуемые изменения состояния,
  • упрощать отладку,
  • оптимизировать ререндеринг.

Пример:

<task-list>
  <ul>
    { this.tasks.map(task => <li>{ task.name }</li>) }
  </ul>

  <script>
    this.tasks = [
      { name: 'Сделать домашнее задание' },
      { name: 'Прочитать документацию' }
    ]
  </script>
</task-list>

Здесь tasks — массив объектов. Любое изменение массива (например, добавление нового элемента через this.tasks.push({name: 'Новая задача'})) автоматически обновляет список <ul>. Данные управляют интерфейсом, а не наоборот.


Реактивные массивы и методы обновления

Для массивов и объектов Riot.js отслеживает изменения через методы-обертки, которые вызывают повторный рендер компонента:

  • push, pop, shift, unshift, splice, sort, reverse
  • this.update() для ручного обновления при сложных изменениях

Пример с динамическим добавлением задачи:

<task-list>
  <input ref="newTask" placeholder="Добавить задачу">
  <button oncl ick={addTask}>Добавить</button>
  <ul>
    { this.tasks.map(task => <li>{ task.name }</li>) }
  </ul>

  <script>
    this.tasks = []

    this.addTask = () => {
      if (this.refs.newTask.value) {
        this.tasks.push({ name: this.refs.newTask.value })
        this.refs.newTask.value = ''
      }
    }
  </script>
</task-list>

Вызов push автоматически приводит к обновлению списка <ul>. Объекты и массивы становятся реактивными без сложной настройки.


Примитивные типы и реактивность

Для примитивов (числа, строки, булевы значения) реактивность обеспечивается через прямое присваивание this.property = value. В случае сложных операций над объектами или вложенными структурами требуется ручное обновление через this.update().

this.user = { name: 'Иван', age: 25 }

// Изменение вложенного свойства требует обновления компонента
this.user.age++
this.update()

Без вызова this.update() изменение age не отразится в шаблоне.


Обновление компонентов и оптимизация

Riot.js использует виртуальный DOM на основе парсинга шаблона, что позволяет минимизировать реальные изменения в DOM. Каждое изменение состояния компонента вызывает:

  1. Определение, какие части DOM зависят от измененных данных.
  2. Сравнение нового значения с текущим DOM.
  3. Обновление только изменившихся узлов.

Это обеспечивает высокую производительность при сложных интерфейсах с большим количеством компонентов.


События и реактивность

Односторонняя привязка не исключает взаимодействия с пользователем. Все изменения состояния происходят через обработчики событий:

<toggle-switch>
  <button oncl ick={toggle}>{ this.active ? 'Включено' : 'Выключено' }</button>

  <script>
    this.active = false

    this.toggle = () => this.active = !this.active
  </script>
</toggle-switch>

Состояние active изменяется через метод toggle, что запускает реактивное обновление интерфейса. При этом поток данных всегда остаётся от модели к представлению.


Итоговая схема работы

  1. Инициализация состояния через this.
  2. Привязка данных к шаблону через фигурные скобки {}.
  3. Реактивное обновление при изменении состояния.
  4. Односторонний поток данных: DOM отображает состояние, но не изменяет его напрямую.
  5. Обработка пользовательских действий через события, которые изменяют состояние.

Riot.js сочетает минимализм и мощную реактивность, позволяя создавать динамические интерфейсы с предсказуемым управлением состоянием.