Как уведомлять подписчиков после каждого изменения состояния?
Что проверяют
Знаете ли вы паттерн «Наблюдатель» не по учебнику, а в том виде, в каком он живёт внутри Redux, Zustand и useSyncExternalStore. Следом обычно просят реализовать отписку и спрашивают, как не перерисовывать весь интерфейс на каждое изменение.
Как отвечать
Минимальный стор держит состояние и множество слушателей:
subscribe(listener): добавляет функцию вSetи возвращает функцию отписки. Без неё размонтированные компоненты продолжат получать события, это утечка памяти;setState(updater): вычисляет новое состояние иммутабельно, сравнивает со старым черезObject.isи уведомляет только при реальном изменении;- обход слушателей: по копии набора,
[...listeners].forEach(l => l()), чтобы отписка внутри колбэка не ломала итерацию; getState(): слушатель получает сигнал «что-то поменялось» и сам читает нужный срез.
В React такой стор подключают через useSyncExternalStore(subscribe, getSnapshot): хук появился в React 18 и защищает от рассинхронизации при конкурентном рендеринге. Требование к нему: getSnapshot возвращает одну и ту же ссылку, пока данные не изменились, иначе React уйдёт в бесконечные перерисовки.
Дальше про производительность. Чтобы компонент не перерисовывался от чужих изменений, используют селекторы с проверкой равенства, как useStore(selector) в Zustand. Если изменения идут пачкой, уведомления группируют через queueMicrotask. Ошибку в одном слушателе ловят try/catch, чтобы она не оборвала оповещение остальных. Для событий между независимыми частями приложения подходит и стандартный EventTarget с CustomEvent.
Где спотыкаются
Удаляют слушателя из массива через splice прямо во время обхода, например когда колбэк отписывается сам после первого вызова. Индексы сдвигаются, и следующий слушатель молча пропускается.
Мутируют состояние на месте, а затем уведомляют. Подписчики, сравнивающие ссылки, не видят изменений, и React не перерисовывает компонент.
Уведомляют всех на каждое изменение и отдают им всё состояние целиком. На десятках компонентов это превращается в лишние рендеры, которые потом ищут в профайлере.