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

Flux — это архитектурный паттерн, разработанный Facebook для управления состоянием приложений с пользовательским интерфейсом. Основная цель Flux — упрощение управления сложными состояниями за счёт однонаправленного потока данных. В отличие от традиционных MVC-подходов, Flux предотвращает двусторонние зависимости между компонентами, что делает приложение более предсказуемым и легко отлаживаемым.


Основные компоненты Flux

Flux состоит из нескольких ключевых сущностей, каждая из которых выполняет строго определённую функцию:

1. Actions (Действия)

Actions — это объекты, содержащие описание события, которое произошло в приложении. Обычно они включают:

  • type — строковой идентификатор действия
  • payload — дополнительные данные, необходимые для обработки действия

Пример Action в Jav * aScript:

const addTodoAction = {
  type: 'ADD_TODO',
  payload: { id: 1, text: 'Купить хлеб' }
};

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

2. Dispatcher (Диспетчер)

Dispatcher — это центральный механизм, который распределяет Actions по всем зарегистрированным Stores. Он обеспечивает, что все Stores получают одно и то же событие в согласованном порядке.

Пример использования Dispatcher:

import { Dispatcher } from 'flux';

const dispatcher = new Dispatcher();

dispatcher.register((action) => {
  switch(action.type) {
    case 'ADD_TODO':
      console.log('Добавление задачи:', action.payload);
      break;
  }
});

Dispatcher не содержит логики изменения состояния — его задача исключительно в маршрутизации Actions.

3. Stores (Хранилища)

Stores хранят состояние приложения и бизнес-логику. Каждый Store подписан на Dispatcher и обновляет своё состояние при получении соответствующих Actions. После изменения состояния Store уведомляет подписчиков (например, компоненты UI) через события.

Пример Store для управления списком задач:

import { EventEmitter } from 'events';
import dispatcher from './dispatcher';

class TodoStore extends EventEmitter {
  constructor() {
    super();
    this.todos = [];
  }

  getTodos() {
    return this.todos;
  }

  handleAction(action) {
    switch(action.type) {
      case 'ADD_TODO':
        this.todos.push(action.payload);
        this.emit('change');
        break;
    }
  }
}

const todoStore = new TodoStore();
dispatcher.register(todoStore.handleAction.bind(todoStore));

export default todoStore;

Ключевой момент: Stores содержат логику изменения состояния, но не знают о UI. Это гарантирует независимость состояния от представления.

4. Views (Представления)

Views — это компоненты UI, которые подписываются на изменения Stores. При получении события изменения состояния View обновляет отображение данных. В контексте Inferno Views обычно реализуются как компоненты, которые используют состояние Store через подписку.

Пример подписки View на Store в Inferno:

import { Component } from 'inferno';
import todoStore from './TodoStore';

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: todoStore.getTodos()
    };
  }

  componentDidMount() {
    todoStore.on('change', () => {
      this.setState({ todos: todoStore.getTodos() });
    });
  }

  render() {
    return (
      <ul>
        {this.state.todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
      </ul>
    );
  }
}

Принципы однонаправленного потока данных

Flux основан на строгом однонаправленном потоке данных:

  1. Пользователь взаимодействует с UI.
  2. View генерирует Action с данными события.
  3. Action передаётся Dispatcher.
  4. Dispatcher уведомляет все Stores о новом Action.
  5. Stores обновляют состояние и испускают событие изменения.
  6. View подписаны на изменения и обновляют своё отображение.

Преимущества:

  • Прогнозируемость состояния: каждое изменение проходит через Actions.
  • Легкость тестирования: Stores можно тестировать изолированно.
  • Упрощённая отладка: можно отследить цепочку изменений через Actions.

Интеграция Flux с Inferno

Inferno поддерживает работу с Flux аналогично React. Основные рекомендации:

  • Использовать Stores для глобального состояния вместо прямого хранения состояния в компонентах.
  • Actions должны быть минимальными и содержать только данные события.
  • Dispatcher может быть единственным экземпляром для всего приложения.
  • Views остаются максимально простыми, только отображают данные и инициируют Actions.

Пример создания глобального TodoStore с Inferno:

import { createStore } from 'flux/utils';
import dispatcher from './dispatcher';

class TodoStore extends createStore {
  constructor() {
    super(dispatcher);
    this.todos = [];
  }

  __onDispatch(action) {
    if (action.type === 'ADD_TODO') {
      this.todos.push(action.payload);
      this.__emitChange();
    }
  }

  getTodos() {
    return this.todos;
  }
}

export default new TodoStore();

Практические советы по архитектуре Flux

  • Разделять Action Creators и сами Actions. Action Creator — функция, которая формирует Action и отправляет его через Dispatcher.
  • Минимизировать количество глобальных Stores: каждая область приложения может иметь свой Store.
  • Избегать сложной логики в Views: все вычисления состояния должны выполняться в Stores.
  • Использовать именованные события для Stores ('change', 'update'), чтобы обеспечить гибкость подписки компонентов.

Flux обеспечивает прозрачную, предсказуемую систему управления состоянием с однонаправленным потоком данных, что особенно важно в крупных приложениях на Inferno, где компоненты часто обновляют состояние независимо друг от друга. Правильная организация Actions, Dispatcher, Stores и Views позволяет строить масштабируемые интерфейсы без хаоса в состоянии.