State Management

Colocation и производительность

Команда замечает, что при вводе одной буквы в поле поиска перерисовывается половина экрана: список, шапка, боковое меню. Причина не в React и не в Zustand. Причина в том, что строка поиска лежит в глобальном store, на который подписано слишком многое. Стоит опустить это состояние в сам компонент поиска - и ввод перестаёт трогать остальной экран. Производительность здесь определяется не выбором библиотеки, а тем, как высоко поднято состояние и насколько узко его читают.

  • Поле поиска или фильтра, опущенное из глобального store в локальный useState, перестаёт перерисовывать экран
  • Список с тысячами строк, где каждая строка подписана узким селектором на свою часть, а не на весь массив
  • Дашборд, где панель темы и панель данных читают разные срезы store и не задевают друг друга
  • Форма, где значение каждого поля живёт локально, а наверх поднят лишь итог отправки
  • Команды, у которых ввод в одно поле раньше тормозил весь интерфейс из-за слишком общего состояния

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

  • Производное состояние вычисляется, а не хранится отдельной копией
  • Подъём состояния наверх к общему предку для совместного доступа
  • Идея о том, что лишний ре-рендер это работа, которой можно избежать
  • Производное состояние

Принцип colocation

Colocation это принцип держать состояние как можно ближе к месту, где оно используется. Если значение нужно одному компоненту, оно живёт внутри него через useState. Если двум соседним - его поднимают к ближайшему общему предку, но не выше. Цель в том, чтобы область видимости состояния совпадала с областью его реального использования, не больше.

Ввод в поле меняет только локальный query, и перерисовывается лишь сам SearchBox. Наверх через onSubmit уходит только итог - готовый запрос, а не каждое нажатие клавиши. Состояние процесса ввода осталось рядом с вводом, а наружу вышел лишь результат.

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

Что означает принцип colocation применительно к состоянию?

Цена чрезмерной глобализации

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

  • Состояние локально (colocation) — Значение живёт в компоненте. Его меняет и читает только он, область ре-рендера минимальна, источник изменения очевиден из кода рядом
  • Состояние преждевременно глобально — Значение в общем store, хотя нужно одному месту. Меняться может из любой точки, на него подписаны лишние компоненты, и каждое изменение задевает больше, чем следует

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

Простая проверка: если у значения ровно один потребитель и нет признаков скорого появления второго, ему не место в глобальном store. Локальный useState даёт меньшую область ре-рендера и более понятный поток изменений.

Почему преждевременная глобализация состояния вредит производительности?

Гранулярность селекторов и ре-рендеры

Когда значение всё же глобально и живёт в store, вступает в игру гранулярность селектора. Селектор решает, на какой кусок store подписан компонент. Узкий селектор на один примитив означает ре-рендер только при изменении этого примитива. Широкий селектор, возвращающий большой объект, перерисовывает компонент при изменении любой его части.

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

useShallow сравнивает выбранные поля поверхностно по значениям, а не по ссылке нового объекта. Так компонент может читать несколько полей и при этом перерисовываться только при их реальном изменении. Чем уже и точнее селектор, тем меньше лишних ре-рендеров, и большой общий store перестаёт быть проблемой производительности.

Селектор, который на каждом вызове возвращает новый массив или объект из нескольких полей, сводит на нет всю оптимизацию: ссылка каждый раз новая, и компонент ре-рендерится при любом изменении store. Для нескольких полей нужно поверхностное сравнение, для одного примитива оно не требуется.

Компонент читает из store два поля и оборачивает их в новый объект { a: s.a, b: s.b } без поверхностного сравнения. Что произойдёт?

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

Этот урок про близость состояния. Рядом стоят выбор инструмента и производное состояние:

  • Производное состояние — Не хранить то, что можно вычислить, это часть принципа держать состояние минимальным и локальным
  • Как выбрать инструмент — Дерево решений отправляет в глобальный store лишь действительно глобальное, colocation решает, что им не является

Итог

  • Colocation это принцип держать состояние как можно ближе к месту использования, в идеале в самом компоненте
  • Состояние поднимают наверх только при появлении реального второго потребителя, а не на всякий случай
  • Чрезмерная глобализация раздувает общий стейт и заставляет лишние компоненты перерисовываться
  • Гранулярность селектора решает, на какой кусок подписан компонент, и тем самым когда он ре-рендерится
  • Узкий селектор на примитив сравнивается по значению, выбор нескольких полей оборачивают в поверхностное сравнение
  • Производительность состояния это в первую очередь где оно живёт и насколько узко читается, а не какая библиотека

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

  • sm-04-derived-state — Производное состояние не хранят, а вычисляют, и это первый шаг к тому, чтобы держать первичное состояние локально и узким
  • sm-40-choosing — Дерево решений отправляет действительно глобальное в общий store, а colocation определяет, что таковым не является
Colocation и производительность

0

1

Войти