Как реализовать передачу нового состояния первым аргументом в callback при подписке?
Что проверяют
Речь о контракте подписки: слушатель получает новое состояние первым аргументом, а вопрос в деталях. Нужно ли звать его сразу при подписке с текущим значением, что лежит во втором аргументе и как отписаться. Следом обычно спрашивают, что будет, если слушатель отпишется или бросит исключение прямо во время рассылки.
Как отвечать
Каркас реализации:
- хранилище слушателей:
Set, а не массив, чтобы повторная подписка той же функции не дублировала вызовы; - сигнатура:
listener(state, prevState). Новое состояние первым, потому что оно нужно почти всегда, а предыдущее только для сравнения; - немедленный вызов: решение, которое нужно проговорить явно. Svelte-сторы по контракту вызывают подписчика синхронно при подписке с текущим значением.
BehaviorSubjectв RxJS делает то же, обычныйSubjectнет. В Zustand слушатель по умолчанию срабатывает только на изменения, аfireImmediatelyесть в middlewaresubscribeWithSelector. В своём сторе удобно сделать опцию{ immediate: true }; - отписка:
subscribeвозвращает функцию, которая удаляет слушателя изSet.
Рассылку делайте по копии: [...listeners].forEach(l => l(state, prev)). Тогда слушатель, который отписался или подписал нового во время вызова, не ломает обход. Ошибку одного слушателя ловите в try/catch и логируйте, чтобы она не оставила остальных без уведомления.
В React такой источник подключается через useSyncExternalStore, и там аргумент слушателя не используется: React сам вызывает getSnapshot.
Где спотыкаются
Замыкают состояние в эффекте с пустыми зависимостями и вычисляют новое значение от старой переменной, как value + 1 внутри useEffect(..., []). Слушатель всегда видит начальное значение. Новое состояние должно приходить аргументом или через функциональное обновление.
При немедленном вызове зовут слушателя до того, как subscribe вернул функцию отписки. Слушателю, который хочет отписаться на первом же значении, ещё нечем это сделать.
Забывают вернуть очистку из useEffect. Каждое монтирование компонента, а в StrictMode ещё и повторный запуск эффекта в разработке, добавляет нового слушателя, и обновления приходят дважды.