Неизменяемость состояния и чистые функции

В Hyperapp состояние приложения (state) рассматривается как неизменяемый объект. Любые изменения не производятся напрямую, а создаются новые объекты состояния на основе предыдущего. Это позволяет:

  • Отслеживать изменения состояния без сложных мутаций.
  • Обеспечивать предсказуемое поведение приложения.
  • Использовать механизмы оптимизации рендеринга за счёт сравнения старого и нового состояния.

Пример структуры состояния:

const state = {
  counter: 0,
  todos: [
    { id: 1, text: "Изучить Hyperapp", done: false },
    { id: 2, text: "Написать статью", done: false }
  ]
};

Изменение counter через мутацию напрямую:

state.counter = state.counter + 1; // НЕПРАВИЛЬНО

Правильный способ с использованием чистой функции:

const increment = state => ({ ...state, counter: state.counter + 1 });

В этом примере создаётся новый объект состояния с обновлённым значением counter, а оригинальный объект остаётся неизменным.


Чистые функции и их роль

Чистая функция — это функция, которая:

  1. Не изменяет аргументы напрямую.
  2. Возвращает новый результат, полностью зависящий от входных данных.
  3. Не имеет побочных эффектов.

Применение чистых функций в Hyperapp позволяет:

  • Легко тестировать логику приложения.
  • Использовать концепцию «action» для управления состоянием.
  • Поддерживать прозрачность работы и предсказуемость.

Пример чистой функции в Hyperapp:

const toggleTodo = id => state => ({
  ...state,
  todos: state.todos.map(todo =>
    todo.id === id ? { ...todo, done: !todo.done } : todo
  )
});

Здесь функция toggleTodo создаёт новый массив todos, где только выбранный элемент изменён, остальные остаются неизменными. Исходный массив state.todos не модифицируется.


Actions как чистые функции

В Hyperapp все изменения состояния выполняются через actions. Action — это функция, которая принимает текущее состояние и возвращает новое. Action должен быть чистой функцией:

const addTodo = text => state => ({
  ...state,
  todos: [
    ...state.todos,
    { id: Date.now(), text, done: false }
  ]
});

Ключевые моменты:

  • Используется оператор spread для создания нового массива todos.
  • Каждый элемент массива копируется, чтобы не нарушить принцип неизменяемости.
  • Action возвращает новый объект состояния, не изменяя старый.

Связь неизменяемости и виртуального DOM

Hyperapp использует виртуальный DOM для эффективного обновления реального DOM. Важный аспект:

  • Hyperapp сравнивает старое и новое состояние.
  • На основе изменений строится минимальный набор операций для реального DOM.
  • Если состояние изменяется напрямую (мутация), Hyperapp может пропустить обновление, так как старое и новое состояние остаются ссылочно одинаковыми.

Пример правильного обновления DOM через неизменяемость:

const state = { counter: 0 };

const increment = state => ({ ...state, counter: state.counter + 1 });

app({
  init: state,
  view: state => h("div", {}, `Counter: ${state.counter}`),
  node: document.getElementById("app"),
  actions: { increment }
});

При каждом вызове increment создаётся новый объект состояния, что гарантирует корректное обновление DOM.


Глубокие изменения состояния

Когда состояние вложенное, важно создавать копии на каждом уровне. Прямая мутация вложенных объектов нарушает принцип неизменяемости.

Пример с вложенными объектами:

const state = {
  user: { name: "Alice", settings: { theme: "dark", notifications: true } }
};

// Неправильно:
state.user.settings.theme = "light";

// Правильно:
const updateTheme = theme => state => ({
  ...state,
  user: {
    ...state.user,
    settings: {
      ...state.user.settings,
      theme
    }
  }
});

Использование оператора spread на каждом уровне обеспечивает полное соблюдение принципа неизменяемости.


Итоги по принципам Hyperapp

  • Состояние неизменно: любые изменения создают новый объект.
  • Actions — чистые функции: они не мутируют входные данные, возвращают новое состояние.
  • Побочные эффекты отделены: вычисления и рендеринг зависят только от состояния.
  • Вложенные объекты копируются: глубина копирования зависит от структуры состояния.
  • Оптимизация рендеринга: виртуальный DOM использует сравнение ссылок, поэтому неизменяемость критична.

Придерживаясь этих принципов, Hyperapp позволяет создавать приложения с предсказуемым поведением, легко тестируемой логикой и эффективным обновлением интерфейса.