Skip to content

Instantly share code, notes, and snippets.

@XaveScor
Last active February 2, 2024 10:57
Show Gist options
  • Save XaveScor/99431c573b53b8a0c41fb3b5fec522bc to your computer and use it in GitHub Desktop.
Save XaveScor/99431c573b53b8a0c41fb3b5fec522bc to your computer and use it in GitHub Desktop.
Context vs StateManager

По мотивам вопросов чатика 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. Что делать? Здесь всё просто:

  1. Ищем компонент A, в котором мы храним shared state.
  2. 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 будет состоять только из одного пункта:

  1. Просто используем 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. С этим уже можно жить, если не задумываться о:

  1. Наше решение очень-очень похоже на редакс. Почему бы не взять его?
  2. В редаксе есть оптимизации, которые позволяют ему работать куда лучше, к примеру combineReducers.ts#L192 - не пересоздаём те части стейта, которые не были изменены
  3. У биндингов к реакту react-redux есть уже готовые хуки по типу useSelector, которые не ререндерят компонент, если стейт не сохранился.
  4. У react-redux есть какие-никакие девтулзы, которые позволяют дебажить приложение.

Беря во внимание как минимум эти 4 пункта я не вижу смысла советовать пились собственное редаксоподобное решение, которое будет ничем не лучше, чем сам редакс во всём, кроме одного пункта: суммарный размер вашего решения будет меньше чем у редакса - это правда. Но в остальном полные минусы, так что не нужно изобретать то, что уже изобретено. У вас стоит задача написать приложение, а не написать лучший стейт менеджер.

Вывод

Не нужно использовать контекст как стейт менеджер

У вас стоит задача написать приложение, а не написать лучший стейт менеджер. В ходе разработки своего приложения вы неизбежно придёте к тому, что изобретёте собственный стейт менеджер.

Вы хотите уменьшить размер зависимостей?

Если у вас маленький проект, то стоит взять что-то попроще, к примеру, Svelte. Так вы получите очень маленький размер бандла.

А возможно у вас вообще нет динамики, поэтому лучше просто нагенерить статических страниц с помощью Hugo

Если у вас большой проект, то за небольшой размер зависимостей вы отплатите увеличившимся размером проекта. В итоге поменяете шило на мыло.

@it-xp
Copy link

it-xp commented May 16, 2020

Интересный гист, но вообще это как спорить что важнее в теле, руки, или ноги?

Использовать надо и то и то, просто для разных целей.

Я не стану расписывать прям все юзкейсы, но поверхностно пробегусь

Зачем использовать стейт менеджер? Конечно же для реализации бизнес логики конкретного приложения

Зачем использовать контекст? например если надо разработать сложный реюзабельный компонент.

Сразу же возникает вопрос, а что если этот реюзабельный компонент должен часть своего реюзабельного состояния делать доступным другим частям приложения? То есть по сути с позиции реюзабельности хранить данные в контексте, а с точки зрения приложения в стейте?

И вот тут начинаются пляски с пританцовками, так повернёшь - бесполезное дублирование данных, как следствие рассадник сложноуловимых багов, а по другому, адская логика, по этому вопрос стоит скорее не что вместо чего использовать, а как подружить так, что бы и то и то работало без лишних "мама роди меня обратно"

Необходимо уяснить, что это не два способа решать одну задачу, а инструменты для решения разных задач

@dmitryshelomanov
Copy link

@userbq201 Если мне не изменяет память, то редакс тоже позволяет бить на сущности. combineReducers для этого и придумали)
Ещё какие аргументы?)

давно не видел гист
вот спор возник и решил вспомнить

и как раз отвечу

да позволяет
но тут есть большое НО
ты аффектишь все подписчики
в то время как контекст не реагирует на это

каждый контекст обособленный

@dmitryshelomanov
Copy link

@dmitryshelomanov
Copy link

и да посоны
берем эффектор и понимаем что редакс и контексты не нужны)

@budarin
Copy link

budarin commented Oct 22, 2020

  • да не согласен я с ним!
  • с кем?
  • да с обоими!
    :)

веселопеды нужны!
веселопеды важны!
без них нет движения вперед :)

https://www.npmjs.com/package/@budarin/use-react-redux

использую на проекте и не страдаю - все фишки redux включая производительность кроме devtools (я ими и в редаксе не особенно пользовался)

@carina-akaia
Copy link

Но к нас внезапно приходит заказчик и говорит, что ему нужна новая фича.

опечаточка

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment