Как реализовать паттерн наблюдателя (observer) для реактивного состояния?
Что проверяют
Понимаете ли вы, как устроены сторы и сигналы под капотом, а не только как их подключать. Следом обычно просят связать свой стор с React и спрашивают, как не допустить лишних перерисовок и утечек.
Как отвечать
Минимальный реактивный стор укладывается в три метода, классы Subject и Observer для этого не нужны:
getState(): возвращает текущий снимок состояния;setState(next): принимает новое значение или функцию от предыдущего, сравнивает черезObject.isи уведомляет подписчиков только при реальном изменении;subscribe(listener): кладёт слушателя вSetи возвращает функцию отписки. Такой контракт у Redux и Zustand, и его ждут интеграции с фреймворками.
Состояние обновляется иммутабельно: setState(s => ({ ...s, count: s.count + 1 })). Новая ссылка и есть сигнал об изменении, на ней держатся и сравнение в сторе, и memo в компонентах.
Для React есть готовый мост: useSyncExternalStore(store.subscribe, store.getState). Он появился в React 18 и решает проблему «разрыва», когда при конкурентном рендере разные компоненты видят разные версии внешнего состояния. Для частичной подписки добавьте селектор, чтобы компонент перерисовывался, только когда меняется его кусок.
Хорошо показать, куда развился паттерн: сигналы во Vue, Solid, Preact и Angular это тот же observer, только подписка создаётся автоматически при чтении значения внутри эффекта или вычисляемого значения.
Где спотыкаются
Мутируют состояние на месте: state.items.push(x) и затем notify(). Ссылка не изменилась, сравнение говорит «ничего не произошло», и часть интерфейса не обновляется.
Не отписываются. Слушатель держит замыкание с компонентом, компонент размонтирован, а память не освобождается. В React отписка должна возвращаться из useEffect или делаться самим useSyncExternalStore.
В getSnapshot возвращают новый объект на каждый вызов, например () => ({ ...store.state }). React считает, что снимок постоянно меняется, и уходит в бесконечные перерисовки с предупреждением в консоли.