Паттерны для глобального состояния

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


Централизованное хранилище (Store)

Централизованное хранилище представляет собой объект, который хранит все данные приложения и предоставляет методы для их чтения и изменения. Основные принципы работы:

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

  2. Наблюдаемое состояние Riot.js позволяет реализовать реактивное хранилище с помощью событий. Изменения в store могут автоматически оповещать подписанные компоненты.

Пример реализации простого store:

const store = {
  state: {
    user: null,
    todos: []
  },
  listeners: [],
  
  subscribe(listener) {
    this.listeners.push(listener);
  },
  
  notify() {
    this.listeners.forEach(listener => listener(this.state));
  },
  
  setState(newState) {
    this.state = { ...this.state, ...newState };
    this.notify();
  }
};

Компонент может подписываться на изменения:

import { component } from 'riot';

component('todo-list', {
  onMounted() {
    store.subscribe(state => {
      this.update({ todos: state.todos });
    });
  }
});

Паттерн Flux

Riot.js легко интегрируется с паттерном Flux. Основные элементы:

  • Action — объект с информацией о событии, которое изменяет состояние.
  • Dispatcher — посредник, который отправляет действия в store.
  • Store — хранит состояние и обрабатывает действия.
  • View/Component — подписывается на изменения store.

Пример Action и Dispatcher:

const Dispatcher = {
  callbacks: [],
  
  register(callback) {
    this.callbacks.push(callback);
  },
  
  dispatch(action) {
    this.callbacks.forEach(cb => cb(action));
  }
};

const Actions = {
  addTodo(text) {
    Dispatcher.dispatch({ type: 'ADD_TODO', payload: text });
  },
  
  removeTodo(id) {
    Dispatcher.dispatch({ type: 'REMOVE_TODO', payload: id });
  }
};

Store с обработкой действий:

const TodoStore = {
  todos: [],
  listeners: [],
  
  subscribe(listener) {
    this.listeners.push(listener);
  },
  
  notify() {
    this.listeners.forEach(listener => listener(this.todos));
  }
};

Dispatcher.register(action => {
  switch(action.type) {
    case 'ADD_TODO':
      TodoStore.todos.push({ id: Date.now(), text: action.payload });
      TodoStore.notify();
      break;
    case 'REMOVE_TODO':
      TodoStore.todos = TodoStore.todos.filter(todo => todo.id !== action.payload);
      TodoStore.notify();
      break;
  }
});

Реактивные глобальные переменные

Для небольших приложений можно использовать реактивные переменные без полноценного store. Riot.js поддерживает синтаксис observable:

import { observable } from 'riot';

const state = observable({
  count: 0
});

state.on('update', () => console.log('State changed', state.count));

state.count++; // Автоматически вызовет событие 'update'

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


Модуляризация и разделение состояния

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

  • Упрощать поддержку и тестирование.
  • Избегать конфликтов между независимыми функциональными блоками.
  • Снижать связность компонентов.

Пример модуля:

const userModule = observable({
  user: null,
  setUser(newUser) {
    this.user = newUser;
    this.trigger('change', this.user);
  }
});

const todoModule = observable({
  todos: [],
  add(todo) {
    this.todos.push(todo);
    this.trigger('change', this.todos);
  }
});

Компоненты могут подписываться на отдельные модули, не затрагивая другие:

userModule.on('change', user => {
  console.log('Пользователь изменился', user);
});

Рекомендации по использованию глобального состояния

  • Не хранить всё в одном глобальном объекте — это усложняет поддержку.
  • Использовать события и подписки для реактивного обновления компонентов.
  • Разделять состояние на модули по функциональным областям.
  • Применять паттерны Flux или аналогичные для согласованного управления потоками данных.
  • В небольших приложениях можно ограничиваться простыми observable-объектами, но с ростом проекта стоит переходить к централизованным хранилищам.

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