Webrium
Подписаться

Состояние в React: когда нужна библиотека, а когда нет

Состояние бывает четырёх видов, и библиотека нужна только для одного. Как понять, для какого именно, и когда решение вообще пора принимать.

Состояние бывает четырёх видов, и библиотека нужна только одному из нихвиды состояниянужна библиотека

Вопрос «какую библиотеку для состояния взять» задают в начале проекта, когда состояния ещё нет. Отвечать на него в этот момент нечем, и именно поэтому ответ обычно оказывается неверным.

Тупик: выбрать библиотеку заранее

Первое, что пробуют, — выбрать хранилище на старте, вместе с остальным набором инструментов. Логика понятная: потом будет некогда, а переезжать дорого.

Это не помогает по простой причине: библиотеки состояния решают разные задачи, и какая из задач у вас появится, на старте неизвестно. Через полгода выясняется, что взяли инструмент для одного вида состояния, а болит другой — и живёте вы теперь с двумя, потому что переезжать действительно дорого.

Второй частый ход — не брать ничего и держать всё в компонентах, передавая вниз через свойства. Работает, пока дерево неглубокое. Дальше появляется передача через пять уровней ради одного значения в конце, и её начинают чинить контекстом, а контекст перерисовывает всё поддерево при каждом изменении. Получается медленно, и вину списывают на фреймворк.

Четыре вида состояния, а не одно

Слово «состояние» покрывает вещи, у которых мало общего. Разделив их, вы обычно обнаруживаете, что библиотека нужна не для всего, а для одной части — или не нужна вовсе.

Серверные данные. То, что пришло с сервера: списки, карточки, справочники. У них есть свойства, которых нет у остального: они устаревают, их надо перезапрашивать, они могут прийти с ошибкой, и один и тот же набор нужен в разных местах. Это не состояние приложения, это кеш чужих данных, и относиться к нему надо как к кешу.

Состояние интерфейса. Открыт ли выпадающий список, какая вкладка активна, что введено в поле. Живёт недолго, принадлежит одному месту, никому больше не нужно. Почти всегда должно оставаться в компоненте.

Состояние адреса. Фильтры, страница, сортировка, выбранный элемент. Его особенность в том, что оно должно переживать перезагрузку и попадать в ссылку, которой делятся. Место для него — адресная строка, а не хранилище: любое другое решение ломает ссылку.

Общее состояние приложения. Кто вошёл, какая тема, содержимое корзины. Немного по объёму, нужно во многих местах, меняется редко. Вот это и есть та часть, ради которой берут библиотеку.

Разложив состояние по четырём коробкам, легко увидеть главное: большая часть того, что собирались класть в хранилище, туда не относится.

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

Когда библиотека действительно нужна

Признак не «проект большой», а более узкий: одно и то же значение нужно нескольким удалённым друг от друга частям дерева, меняется в одном месте и читается в других, и передача через свойства уже превратилась в передачу ради передачи.

Второй признак — производительность. Если общее значение лежит в контексте и его изменение перерисовывает половину приложения, библиотека с точечными подписками решает ровно эту задачу. Но заметьте: это следствие измерения, а не предположения.

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

Если ни одного признака нет, библиотека добавит слоя абстракции и ничего не решит.

Стоит назвать и то, что признаком не является. Размер команды: библиотека не заменяет договорённостей о том, где что лежит, и в большой команде без таких договорённостей хранилище превращается в свалку быстрее, чем в маленькой. Количество экранов: двадцать независимых экранов, не делящих между собой ничего, прекрасно живут без общего состояния. И привычка: «мы всегда так делали» — довод про людей, а не про задачу, и его стоит называть своим именем, когда он звучит.

Что выбрать, когда нужна

Сравнивать стоит не по названиям, а по трём вопросам.

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

Как устроено обновление: меняется ли состояние напрямую или через описание изменения. Первое короче в записи, второе даёт историю изменений и упрощает разбор ошибок в сложных случаях.

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

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

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

Как понять, что ошиблись

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

Второй: чтобы понять, откуда взялось значение на экране, приходится идти по цепочке из нескольких файлов. Хранилище задумывалось как способ упростить, а стало способом спрятать.

В обоих случаях лечение — не смена библиотеки, а возврат части состояния туда, где ему место: в компонент, в адрес, в инструмент для серверных данных.

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

Позиция редакции

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

Мы неправы, если приложение с самого начала строится вокруг сложного клиентского состояния, которое живёт долго и меняется часто: редактор, таблица с расчётами, интерфейс реального времени. Там хранилище — часть архитектуры, и откладывать выбор не стоит.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Поле разделено швом «use client»: элементы-пропсы пересекают границу между сервером и клиентом и становятся сетевым контрактомсерверклиент
Предыстория

Серверные компоненты: год в продакшене

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

На шкале времени жизни экрана величина — момент, когда становятся известны входные данные, — пробивает пороговую линию между сервером и клиентом: если данные изсервер/клиентвремя жизни экрана
Практика

Где проходит граница клиент — сервер

Куда именно провести границу в своём приложении: не по типам компонентов, а по моменту, когда становятся известны входные данные.

Сетка ячеек с заполненной частью изображает вес сборки, а рядом стоит отдельный счётчик другой величины — путь до первого действия: одно число больше не определпуть до действия
Спор

Размер бандла больше не главная метрика

Против привычной метрики: война с общим весом сборки не ускоряет приложение, и в статье сказано, при каких условиях размер всё же важен.

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

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть