Какие способы типизации подписчиков с передачей нового состояния существуют?
Что проверяют
Умеете ли вы на TypeScript описать паттерн «наблюдатель» так, чтобы подписчик получал правильный тип состояния и не мог его сломать. Следом обычно просят набросать маленький типизированный стор с subscribe или объяснить useSyncExternalStore.
Как отвечать
Вопрос не про выбор между Redux и MobX, а про форму контракта между источником и подписчиком:
- колбэк с новым и прошлым состоянием:
type Listener<S> = (state: S, prev: S) => void, аsubscribeвозвращает() => voidдля отписки. Так устроенsubscribeв Zustand; - подписка через селектор:
subscribe<T>(selector: (s: S) => T, listener: (value: T, prev: T) => void). Тип среза выводится из селектора, а слушатель зовётся, только если значение изменилось поObject.isили своей функции сравнения; - карта событий:
type Events = { login: User; logout: void }иon<K extends keyof Events>(type: K, handler: (p: Events[K]) => void). Имя события и тип данных связаны, опечатку ловит компилятор. Так типизирована библиотекаmitt; - размеченное объединение:
{ type: 'added'; item: Todo } | { type: 'removed'; id: string }, подписчик сужает тип вswitch, а проверка наneverвdefaultне даст забыть новый вариант; - pull-модель: подписчик получает только сигнал и сам читает состояние. Так работают
store.subscribeв Redux (слушатель без аргументов, дальшеgetState()) иuseSyncExternalStore(subscribe, getSnapshot); - Observable:
BehaviorSubject<S>из RxJS хранит текущее значение и сразу отдаёт его новому подписчику.
Состояние для подписчиков отдавайте как Readonly<S>, чтобы случайная мутация не прошла молча.
Где спотыкаются
Перечисляют библиотеки вместо типов. Вопрос про сигнатуры, и ответ без дженериков звучит мимо.
Пишут (state: any) => void или тип Function. Такая подписка компилируется с чем угодно, а ошибку найдёт пользователь.
Объявляют обработчик методом: { onChange(s: S): void }. Параметры метода TypeScript проверяет бивариантно даже со strictFunctionTypes, и несовместимый обработчик пройдёт. Свойство onChange: (s: S) => void проверяется строго.