По мотивам вопросов чатика react.js@telegram
Оффтоп: пожалуйста, не нужно в сотый раз объяснять уже набившую оскомину тему новичку, который задаст подобный вопрос. Просто поделитесь ссылкой на этот текст. С уважением, Андрей @XaveScor Звёздочка
Краткий ответ на этот вопрос, если вы не хотите разбираться детальнее:
- Если вы экспериментируете, то можете взять в качестве стейт-менеджера что угодно. Буквально. Опыт лишним не будет.
- Если вы ищете решение для проекта, на котором планируете зарабатывать деньги, то ни в коем случае не берите контекст в расчёт.
После стабилизации контекста и появления хуков в реакте появилась куча людей с одинаковым вопросом: можно ли контекстом заменить реатом/эффектор/редакст/мобыкс/ваш_любимый_стейт_менеджер? И если да, то стоит ли?
Ответ прост: да. Но возникает вопрос: стоит ли?
Давайте рассмотрим ситуацию, что у нас для работы со стейтом используется контекст и реакт дерево имеет примерно такую структуру:
/-----CHILD_1---------CHILD_1_1
BASE--- \--CHILD_1_2
\------CHILD_2
У компонентов CHILD_1_1 и CHILD_1_2 есть общий стейт, который мы храним в CHILD_1, и через контекст пробрасываем его в CHILD_1_1 и CHILD_1_2.
|Shared State here| |useContext here|
|-----------------| |---------------|
v v |
/-----CHILD_1---------CHILD_1_1 |
BASE--- \--CHILD_1_2 <---|
\------CHILD_2
И всё прекрасно. Мы не тащим лишних зависимостей и у нас всё работает. Но к нас внезапно приходит заказчик и говорит, что ему нужна новая фича. Суть этой фичи опустим, но для её реализации нужно иметь наш shared state
в CHILD_2. Что делать? Здесь всё просто:
- Ищем компонент
A
, в котором мы хранимshared state
. - CHILD_2 - это потомок
A
?- Да. Тогда у нас всё хорошо, просто используем useContext в CHILD_2 и радуемся жизни.
- Нет. Тогда боль:
0. Ищем общий предок
B
уA
и CHILD_2. 0. Переносим вB
всю логику работы сshared state
изA
. 0. ИспользуемuseContext
в CHILD_2.
Выглядит просто, не так ли? Возможно, что я уже вам доказал, что ваш_любимый_стейт_менеджер вам не нужен. Но подождите, давайте представим, что у вас не один shared state
, а 20; что у вас все shared state
хранятся не в одном компоненте, а в 10. В таких условиях схема выше для вас выглядит такой же простой? Для меня нет.
Давайте придумаем как нам писать приложение на основе контекста, чтобы с ним было проще работать.
Главная проблема при работе с контекстом - это поиск shared state
и общего предка. Как нам упростить эти шаги? Так как у нас реакт-дерево - это дерево, то у всех компонентов есть общий предок. Давайте хранить все shared state
в root'овом компоненте. И тогда у нас алгоритм проброса shared state
будет состоять только из одного пункта:
- Просто используем useContext в CHILD_2 и радуемся жизни.
Всё прекрасно, правда? Вроде как да. Теперь давайте рассмотрим как должен выглядеть shared state
для удобной работы с ним:
Благодаря редаксу в API реакта появился весьма удобный хук React.useReducer
, на основе которого можно построить shared state
:
const appReducer = (state, event) => {
return {
state1: reducer1(state.state1, event),
state2: reducer2(state.state2, event),
}
}
const AppStoreProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(appReducer, initialState);
return(
<AppDispatchContext.Provider value={dispatch}>
<AppStateContext.Provider value={state}>
{children}
</AppStateContext.Provider>
</AppDispatchContext.Provider>
)
}
Выглядит весьма неплохо, так как мы очень легко расширяем логику путём добавления редьюсеров в appReducer
. С этим уже можно жить, если не задумываться о:
- Наше решение очень-очень похоже на редакс. Почему бы не взять его?
- В редаксе есть оптимизации, которые позволяют ему работать куда лучше, к примеру combineReducers.ts#L192 - не пересоздаём те части стейта, которые не были изменены
- У биндингов к реакту
react-redux
есть уже готовые хуки по типуuseSelector
, которые не ререндерят компонент, если стейт не сохранился. - У
react-redux
есть какие-никакие девтулзы, которые позволяют дебажить приложение.
Беря во внимание как минимум эти 4 пункта я не вижу смысла советовать пились собственное редаксоподобное решение, которое будет ничем не лучше, чем сам редакс во всём, кроме одного пункта: суммарный размер вашего решения будет меньше чем у редакса - это правда. Но в остальном полные минусы, так что не нужно изобретать то, что уже изобретено. У вас стоит задача написать приложение, а не написать лучший стейт менеджер.
У вас стоит задача написать приложение, а не написать лучший стейт менеджер. В ходе разработки своего приложения вы неизбежно придёте к тому, что изобретёте собственный стейт менеджер.
Если у вас маленький проект, то стоит взять что-то попроще, к примеру, Svelte. Так вы получите очень маленький размер бандла.
А возможно у вас вообще нет динамики, поэтому лучше просто нагенерить статических страниц с помощью Hugo
Если у вас большой проект, то за небольшой размер зависимостей вы отплатите увеличившимся размером проекта. В итоге поменяете шило на мыло.
@negamaxi
если мы пытаеся заменить именно Redux, то масштабирование должно происходит за счет редюсеров (у Redux тоже один стор).
Именно. И у вас возникает куча провайдеров, которые будут на каждый чих ререндерить дерево, если конечно, вы не будете забивать костыли по типу React.memo, что плодит кучу бесполезного мусора в реакт-дереве.
Другое дело, что перейти с Redux на useReducer + Context API и продожать пользоваться глобальным хранилищем это то же самое, что пересесть со скутера на мотоцикл и продолжать ездить 40 км/ч.
Я говорю, что не надо использовать в принципе useReducer + Context API. Да, если у вас какой-то тестовый провект, где вы экспериментируете, то без проблем, хоть черта лысого можете вызывать.
React предполагает использование разноуровневых локальных состояний, что помогает, например, не заниматься ручной чисткой состояния, как это часто бывает в Redux.
Верно. И это является оень плохой практикой. Как минимум потому что мы используем 100500 различных практик, вместо того чтобы иметь одно унифицированное решение. В пределе, я вообще за отказ от локального состояния внутри реакт компонентов. Отсутствие локального состояния позволит полностью выкинуть пакеты react и react-dom из рантайма и компилировать реактовский компонент в цепочку нативных вызовов. Так, к примеру, делает Svelte.
С другой стороны, организация локальных состояний тем образом, который описан в документации, лишает возможности влиять одним событием на несколько состояний сразу. Исправить этот недостаток можно при помощи всплывающих событий.
Вместо одного универсального решения мы изобретаем ещё один костыль, который исправляет одну из 100500 проблем.
Благодарю. Я понял, что нужно полностью переписать текст. Ближайшее время займусь этим.