State Management

Jotai на практике

В конструкторе форм каждое поле имеет своё значение, а где-то рядом нужно показать, валидна ли вся форма. В модели единого стора любое изменение одного поля заставляет пересчитывать весь объект состояния. Атомарный подход переворачивает это: каждое поле это отдельный атом, а валидность это производный атом, зависящий только от нужных полей. Меняется одно поле, и пересчитывается ровно то, что от него зависит. Jotai строит состояние из таких атомов: atom это минимальная единица, useAtom подписывает компонент именно на неё.

  • Конструкторы форм: каждое поле отдельный атом, валидность и сводка как производные атомы
  • Сложные фильтры, где каждый критерий это атом, а результат фильтрации производный от них
  • Состояние, разбросанное по дереву, где компоненты подписываются на разные мелкие куски
  • Асинхронная загрузка по параметру через атом, интегрированный с Suspense
  • Динамические коллекции состояний по id через atomFamily без ручного словаря

Предварительные знания

  • Атом как минимальная единица состояния и идея атомарной модели
  • Хуки и подписка компонента на конкретный кусок состояния
  • Промисы и React Suspense на уровне идеи приостановки рендера
  • Атомы

Атомарная модель и место Jotai

Jotai (по-японски состояние) создан Дайси Като в коллективе Poimandres в 2020 году. Идея атомов восходит к Recoil от Facebook, который первым предложил собирать состояние из мелких независимых единиц вместо одного дерева. Recoil оказался тяжёлым и привязанным к экспериментальному API, и к 2025 году был заархивирован, а его рекомендованным преемником стал именно Jotai. Jotai развивает ту же атомарную идею, но проще: atom это объект-описание без ключей-строк, а зависимости между атомами выводятся автоматически по факту чтения. На 2026 год Jotai это основной живой представитель атомарного подхода в React.

atom и useAtom

В Jotai единица состояния это атом, создаваемый функцией atom с начальным значением. Сам атом не хранит данные, это объект-описание, ключ к значению внутри Jotai. Компонент читает и пишет атом через хук useAtom, который возвращает пару из текущего значения и сеттера, по форме как useState. Подписка точечная: компонент ре-рендерится только при изменении именно этого атома.

countAtom это описание, а не значение: одно описание может использоваться многими компонентами, и все они подписаны на одну общую ячейку. В отличие от useState, чьё значение локально для компонента, атом разделяется. При этом синтаксис чтения и записи тот же, поэтому переход с локального состояния на общий атом не меняет привычную форму useState.

Для частичного доступа есть useAtomValue и useSetAtom. useAtomValue только читает значение и подписывает на изменения, useSetAtom только пишет и на изменения не подписывается. Компонент, которому нужно лишь обновлять атом, берёт useSetAtom и не ре-рендерится при изменении значения. Это тонкая настройка подписки на уровне отдельного атома.

Чем атом в Jotai отличается от локального useState?

Производные атомы

Производный атом это атом, значение которого вычисляется из других атомов. Он создаётся через atom с функцией чтения, которой передан get. Внутри функции вызывают get для нужных атомов, и Jotai сам запоминает, какие атомы были прочитаны. Производный атом пересчитывается только при изменении хотя бы одного из прочитанных атомов, а граф зависимостей строится автоматически.

isValidAtom читает nameAtom и emailAtom через get, и эти вызовы регистрируют зависимости. Когда меняется имя или email, валидность пересчитывается. Если бы рядом был, например, themeAtom, его изменение не затронуло бы isValidAtom, потому что внутри он не читается. Это и есть автоматический граф зависимостей: пересчёт привязан к реально прочитанным атомам.

Производный атом может быть и записываемым, если задать вторую функцию с get, set и новым значением. Здесь fahrenheitAtom читается как преобразование из Цельсия, а при записи пересчитывает и обновляет celsiusAtom. Так производный атом становится двусторонним: чтение выводит значение, запись распределяет изменение обратно на исходные атомы.

  • Производный атом — Значение из других атомов, пересчёт только при изменении прочитанных. Зависимости выводятся автоматически по get
  • Селектор в едином сторе — Производное значение из всего дерева состояния. Зависимости задаются входными селекторами вручную

Как Jotai определяет, от каких атомов зависит производный атом?

Асинхронные атомы и Suspense

Атом может возвращать промис: тогда его функция чтения асинхронна. Такой асинхронный атом интегрируется с React Suspense. Пока промис не разрешён, компонент, читающий атом, приостанавливается, и React показывает ближайший fallback. Когда промис разрешится, атом отдаёт уже готовое значение, и компонент рендерится с данными. Состояние загрузки выражается приостановкой, а не ручным флагом.

userAtom зависит от userIdAtom, поэтому при смене id запрос уходит заново, и Suspense снова покажет fallback на время загрузки. Внутри компонента значение уже развёрнуто из промиса, поэтому user.name доступен напрямую, без проверки на загрузку. Граница Suspense берёт на себя то, что в подходе с флагами выражалось ветками isLoading.

Асинхронный атом покрывает разовую загрузку и интеграцию с Suspense, но не заменяет полноценный серверный кэш. Если нужны кэш по ключу, инвалидация и фоновое обновление, это задача query-слоя вроде TanStack Query. Асинхронный атом уместен для простой загрузки по параметру, а не как самописный механизм свежести серверных данных.

Как выражается состояние загрузки при чтении асинхронного атома через useAtomValue внутри Suspense?

atomFamily и когда выигрывает атомарность

atomFamily это фабрика атомов по параметру. Она принимает функцию, создающую атом из параметра, и возвращает функцию, которая по тому же параметру всегда отдаёт один и тот же атом. Это нужно для динамических коллекций: вместо ручного словаря атомов по id atomFamily сама создаёт и кэширует атом на каждый id, и повторный вызов с тем же id вернёт прежний атом.

todoAtomFamily(id) для одного id всегда возвращает один и тот же атом, поэтому два компонента с одинаковым id разделяют состояние, а с разными id работают с независимыми атомами. Изменение одной строки не трогает остальные: каждая подписана на свой атом из семейства. Так динамический список становится набором независимых единиц состояния без общего пересчёта.

Атомарность выигрывает там, где состояние естественно дробится на мелкие независимые куски, и разные компоненты подписаны на разные из них: формы по полям, фильтры по критериям, списки по элементам. Единый стор с селекторами тоже даёт точечные подписки, но описывает состояние как одно дерево. Jotai же собирает его снизу вверх из атомов, и пересчёт по графу зависимостей становится естественной единицей.

  • Атомарная модель (Jotai) — Состояние из множества мелких атомов, граф зависимостей по чтению. Выигрывает при мелком дроблении и разрозненных подписках
  • Единый стор (Zustand, Redux) — Одно дерево состояния, подписка селектором. Удобно, когда состояние логически едино и его проще держать в одном месте

Зачем нужен atomFamily?

Связь с другими темами

Jotai это атомарная модель рядом с единым стором и сигналами:

  • Атомы — Jotai это практическое воплощение атомарной модели: состояние собирается из мелких атомов, а не из одного дерева
  • Zustand — Альтернативная модель лёгкого состояния: один стор с селекторами против множества независимых атомов
  • Сигналы как парадигма — Производные атомы и computed-сигналы решают одну задачу: пересчёт по графу зависимостей, но по-разному привязаны к React

Итог

  • atom это минимальная единица состояния, useAtom подписывает компонент именно на этот атом, как useState на пару из значения и сеттера
  • Производный атом вычисляется из других атомов через функцию get и пересчитывается только при изменении прочитанных атомов
  • Граф зависимостей между атомами выводится автоматически по факту чтения внутри производного атома
  • Асинхронный атом возвращает промис, и useAtom интегрируется с Suspense: рендер приостанавливается до разрешения
  • atomFamily создаёт атомы по параметру (например по id), давая динамические коллекции состояний без ручного словаря
  • Атомарная гранулярность выигрывает, когда состояние мелко дробится и компоненты подписываются на разные маленькие куски

Связанные уроки

  • sm-09-atoms — Понятие атома как минимальной единицы состояния задаёт основу: Jotai это атомарная модель на практике
  • sm-17-zustand — Zustand это один стор с селекторами, Jotai это множество атомов: два разных взгляда на лёгкое состояние
  • sm-10-signals — Производные атомы перекликаются с computed-сигналами: то же отслеживание зависимостей, иная модель привязки к React
Jotai на практике

0

1

Войти