По мотивам вопросов чатика 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
Если у вас большой проект, то за небольшой размер зависимостей вы отплатите увеличившимся размером проекта. В итоге поменяете шило на мыло.
Интересный гист, но вообще это как спорить что важнее в теле, руки, или ноги?
Использовать надо и то и то, просто для разных целей.
Я не стану расписывать прям все юзкейсы, но поверхностно пробегусь
Зачем использовать стейт менеджер? Конечно же для реализации бизнес логики конкретного приложения
Зачем использовать контекст? например если надо разработать сложный реюзабельный компонент.
Сразу же возникает вопрос, а что если этот реюзабельный компонент должен часть своего реюзабельного состояния делать доступным другим частям приложения? То есть по сути с позиции реюзабельности хранить данные в контексте, а с точки зрения приложения в стейте?
И вот тут начинаются пляски с пританцовками, так повернёшь - бесполезное дублирование данных, как следствие рассадник сложноуловимых багов, а по другому, адская логика, по этому вопрос стоит скорее не что вместо чего использовать, а как подружить так, что бы и то и то работало без лишних "мама роди меня обратно"
Необходимо уяснить, что это не два способа решать одну задачу, а инструменты для решения разных задач