Как стать автором
Обновить

Комментарии 23

Недавно работал над проектом где для данных использовался Apollo graphQL и, соответственно, не было редакса за ненадобностью.


Такое себе удовольствие, если честно. Особенно когда у тебя есть роут, на котором, допустим, определяются какие-то специфичные для этого роута вещи, а потом вдруг прилетает требование отобразить их в сайдбаре. Вот начинается веселье.


Ах да, и модалки которые в редаксе делаются императивно dispatch(showModal(modalName)) было очень больно делать декларативно. Есть вроде чистый компонент таблицы какой-нибудь и давай его пачкать стейтом isVisible итд.


А если все делать через кучу провайдеров — так зачем заменять единый стор и SSOT на кучу разных "сервисов". А если они ещё друг от друга будут зависеть, уххх.


Типа как в примере автора — прилетит "хотим при логине показывать нотификацию". Оу, сорян, у нас нотификейшн провайдер ниже аутентификационного.

А если все делать через кучу провайдеров — так зачем заменять единый стор и SSOT на кучу разных «сервисов». А если они ещё друг от друга будут зависеть, уххх.


Чем больше провайдеров, тем, следовательно, и альтернативный вариант с единым стором будет сложнее. Единый стор не будет проще просто из-за того что это единый стор.

Типа как в примере автора — прилетит «хотим при логине показывать нотификацию». Оу, сорян, у нас нотификейшн провайдер ниже аутентификационного.


Поднять провайдер не проблема. Хотя в этом случае можно сделать новый провайдер (специально для этого нотификейшена) или добавить это в логику самого аутентификационного провайдера. Почему бы и нет? Сами по себе провайдеры могут иметь внутри сложную логику.

Единый стор не будет проще просто из-за того что это единый стор.

Единый стор будет проще за счет того, что а) не надо будет синхронизировать провайдеры.


Хотя в этом случае можно сделать новый провайдер (специально для этого нотификейшена) или добавить это в логику самого аутентификационного провайдера.

б) не надо будет дублировать функционал




Не нужно делать все контексты глобальными! Держите состояние как можно ближе к месту к которому оно относиться.

Кажется, что тут из ошибок только ться, но на самом деле это совет смешивать логику и представление. Если вам понадобится изменить представление, то и логику придется менять. Не надо так.




Про модалы — в статье написано, что отображением модала должна заниматься та страница, которая его отображает. Это кажется хорошей идеей, но ровно до тех пор, пока ваш крутой, реюзабельный модал не понадобится на второй странице и вам придется дублировать код его открытия, закрытия, закрытия если запрос отправился итд.


Никому такого не желаю




Про редакс — основная фишка редакса это не избавление от prop-drilling'а. Это разделение мутаций и асинхронности. Я знаю что есть useReducer, но просто чтобы не смущали народ фразами типа "повсеместное использование Редакса связано именно с тем, что он позволил избавиться от проп дриллинга."

Единый стор не будет проще просто из-за того что это единый стор.


Я сначала научился пользоваться контекстами, а потом redux. И я реально вздохнул с облегчением с redux, особенно на больших приложениях. Единый стор в redux хорошо разбивается на модули и разные редьюсеры, это достаточно удобно чтобы поддерживать код. Не скажу что redux самый простой в освоении, и порой кажется громоздким в реализации, но повторюсь, на больших приложениях, это победа.

Сейчас смотрю за развитием recoil js, и effector, так как в redux чтобы управлять состоянием асинхронно нужно подключать всякие thunk, saga, и так далее.
Посмотрите на MobX и вообще офигеете да и вздохнете с облегчением в квадрате.
Ах да, и модалки которые в редаксе делаются императивно dispatch(showModal(modalName)) было очень больно делать декларативно. Есть вроде чистый компонент таблицы какой-нибудь и давай его пачкать стейтом isVisible итд.


import React from 'react';
import ReactDOM from 'react-dom';

const confirmRoot = document.createElement('div');
const body = document.querySelector('body');
body.appendChild(confirmRoot);

const customConfirm = DialogContent =>
  new Promise(res => {
    const giveAnswer = answer => {
      ReactDOM.unmountComponentAtNode(confirmRoot);
      res(answer);
    };
    ReactDOM.render(<DialogContent giveAnswer={giveAnswer} />, confirmRoot);
  });

export default customConfirm;


И потом в коде можно будет вызывать как

customConfirm(Component)/await customConfirm(Component)


Реакт это все что вам нужно для управления состоянием вашего приложения.

Для уровня Hello World и сверх простого Todo List да. А по правде так:
Стейт менеджмент реакта ровно такое же говно, как и redux и ему подобный не реактивный, иммутабильный, примитивный и делитанский булшит, который принципиально не раскрывает богатые возможности JS и заставляет писать лютый и не поддерживаемый говнокод под видом «ну это best practies же». Посмотрите по сторонам, Vue реактивный, Svelte реактивный, но React же не реактивный по сей день и это просто смешно и абсурдно, благо благодаря MobX'у он стал реактивным с 2016 года.
«Держите состояние как можно ближе к месту к которому оно относиться» — я придерживаюсь другой парадигмы, в разработке приложений, в которых участвовал, локальные стейты приводили к дубляжу и сложному контролю. Идея о том, что можно хранить информацию о состоянии приложения в едином стейте, и каждый компонент может в любое время получить информацию о любой части и взять под контроль ее отображение, стала значительным апгрейдом в плане проектирования удобных систем.

Нужно ли закрыть модалку по сабмиту формы, добавить валидации к полям, исходя из значений в форме перерисовать лэйаут, в едином месте настраивать аналитику (вместо интеграции в каждый компонент), получить более низкоуровневый доступ к верстке компонента, сделать слепок состояния и восстановить для различных целей — глобальное состояние удобнее в огромном количестве реальных задач. А локальное, хотя кажется и не очень удобным выносить tooltipIsShown наружу из компонента Tooltip, но в реальности это непередаваемо удобней, когда можно закрывать все тултипы или показывать нужный по независимой от ховера мышки логике, вместо `querySelectorAll('[data-role=«tooltip»]').click()` — и тут еще нужно проверить состояние, чтобы не закрылись/открылись лишние. А такого кода параллельно с Реактом видел неприлично много.

В общем, пришел к убеждению, что любители локальных стейтов не сталкивались с более-менее сложными интерфейсами, так как этот подход проигрывает по всем фронтам. Цельный интерфейс — как живой организм, а дробленый на «черные коробки» — как непредсказуемая система из броуновских движений. Для бэка подойдет, так как там слишком много логики бывает в сервисах, а задач на составление «цельной картины» из состояния микросервисов может никогда и не появиться, а вот для фронтенда это — антипаттерн, так как интерфейс в каждый момент времени — цельная система, а синхронизация состояний — ключевой момент.

По поводу контекстов — думаю, Кент глаза никому не открыл, при изучении Реакта все так или иначе знакомятся с этой основополагающей концепцией) А вот мысль разбивать хранилище на разноуровневые и подключать каждое отдельно — глупость. Что мешает сделать один StateProvider, и внутри компонентов выбирать нужную его часть?

// Don't
const { user } = useAuthenticatedUser()
const { notifications } = useNotifications()

// Do
const { user, notifications } = useStore()


Импортов и кода меньше, возиться с уровнями и отсутствием проброшенного не нужно. А внутри стора можно как угодно скомпоновать логику и методы, удобно разбив по нескольким файлам.

Только вот от стора требуется реактивность, а от коннектора к компоненту — имплементация shouldComponentUpdate, чтобы компонент обновлялся только при изменении нужных ему данных. В случае с чистыми контекстами ни реакций на изменение, ни оптимизированных ререндеров ждать не приходится, да и обкладываться вот таким

 const [count, setCount] = React.useState(0)
 const value = React.useMemo(() => [count, setCount], [count])


и вызывать по функции вроде setCount для изменения одного значения вместо батчей и мутаций подойдет только для странички с каунтером, но не для более-менее сложного сайта.

Спасибо за перевод, понравилось в статье, что автор топит за отказ от Redux — указанный подход через контексты хоть и не удобен, но все-таки более прогрессивен, на мой взгляд.
а вот для фронтенда это — антипаттерн, так как интерфейс в каждый момент времени — цельная система,


Если вы используете Реакт, то, полагаю, используете компоненты — разве компоненты сами по себе уже не дробят приложение на части? Сам по себе компонентный подход предполагает что мы стремимся к тому что собираем наше приложение из отдельных блоков. При чем чем более независимыми являются компоненты — тем, как правило, лучше.

так как интерфейс в каждый момент времени — цельная система, а синхронизация состояний — ключевой момент.


Вам не нужно состояние какой-то отдельной формы на глобальном уровне, зачем синхронизировать то что не должно быть синхронизированным? А если какое-то состояние нужно на глобальном уровне всему приложению — ну тогда мы и оборачиваем соответствующим провайдером все приложение. Такой подход делает состояния иерархическими, мы точно знаем к чему то или иное состояние относиться, а к чему нет — просто видя что тот или иной провайдер оборачивает.

Что мешает сделать один StateProvider, и внутри компонентов выбирать нужную его часть?


Ну, так сейчас большая часть Реакт разработчики и живет — ведь это же все тот же Редакс без редакса. При едином стейте вам нужно применять какой-то подход к центральному состоянию, и каким будет этот подход? Скорее всего это будет что-то редаксо-подобное. Если не применять никакого подхода то очень скоро этот стейт превратиться в ад.

Импортов и кода меньше, возиться с уровнями и отсутствием проброшенного не нужно. А внутри стора можно как угодно скомпоновать логику и методы, удобно разбив по нескольким файлам.


Идея в том чтобы разделять код с большим соответствием бизнес логике — думать не о том что это редюсер, это экшен и тд, а думать о конкретных стейтах. Это провайдер меню, это провайдер формы и тд, прописывая именно в них нужную логику, а не разделять по разным файлам. Это очень похоже на компонентный подход, но вместо компонентов — стейты. Веб разработка не просто так ушла от разделения на html/css/js к компонентному подходу, где у каждого компонента свои стили, логика и тд.

Компьютерам плевать на то как мы организуем свой код, но человеку куда проще мыслить изолированными блоками, а не думать о том как устроена вся система целиком.

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


Что значит «обкладывать» — нам нужно заюзать тот же useMemo (или что угодно еще) в одном месте — в файле контекста, и мы оптимизируем это везде где используется этот контекст.

И да, сам по себе такой подход и приводит к оптимизации — разделяя контексты, вызовы ререндеров будут происходить только при изменении на уровне этого контекста или ниже, Реакт не вызывает ререндер на более высоких уровнях иерархии если изменения произошли на более низких (не важно юзаем мы контексты или проп дриллинг) — нужно стремиться размещать провайдеры/контексты/стейты как можно ниже по иерархии, благодаря чему Реакт, частично, оптимизирует это все сам. А вот если мы запихнем все состояние в один глобальный стейт — у нас начнутся проблемы с оптимизаций. Вот другой мой перевод Кента где подробнее про вопросы оптимизации — habr.com/ru/post/485032
Компонентами можно назвать в том числе и обычные html-теги с атрибутами, например <div class=«myclass» onclick=«alert(1)» /> — тут и определенная семантичность в виде предустановленной блочной модели отображения, и стилизация, и методы, и состояние, которое в данном случае вынесено в стилевой класс (например, display: none или положение на странице). Реакт-компоненты выполняют роль агрегатора нескольких таких компонентов с возможностью управлять более крупными сущностями, которые становятся при этом более семантичными + интегрированными в js.

Но насколько изолированность — это благо? Вот несколько дивов, их стейты находятся в стилевых классах и задают, к примеру, абсолютное положение. Задача — сделать их взаимную раскладку в виде мозаики, причем при наведении на один из элементов он увеличивается и сдвигает остальные. Но так как они независимы и не знают ничего о друг о друге, то это сделать можно только независимыми математическими расчетами в стилях, при этом вбивая отступы в пикселях в качестве констант. А если бы у них был глобальный стейт типа `elements: [{ left, right, width, height }, ...]`, то это можно было бы сделать динамической формулой и гибко задавать условия взаимного расположения и интеракций.

Точно так же это экстраполируется на более крупные компоненты, и примеры реальных задач, в которых нужно знать о внутренних характеристиках и иметь возможность влиять на них, я привел в комментарии выше. Можно даже по примеру из прошлого абзаца — это будет например список нотификаций или галерея, и по клику на кнопку в другой части страницы нужно закрывать все нотификации или вызывать новые, изменять размеры и положение (сортировку) изображений в галерее. Чтобы наглядней — эта управляющая кнопка далекооо, в компонент Notifications / Gallery ее не поместить.

Так что «чем более независимыми являются компоненты — тем, как правило, лучше» — однозначно нет, не лучше. В глобальном стейте вам не помешает дополнительный параметр, а в локальном будет неудобен при возникновении потребности обратиться к нему — почему бы сразу не использовать удобное решение? Вы в целом не можете сделать совсем изолированные компоненты в интерфейсах, у них так или иначе будет общая раскладка, общие слои расположения, взаимное влияние — это совсем не бэк на микросервисах.

«Вам не нужно состояние какой-то отдельной формы на глобальном уровне» — в основном как раз нужно.

«Зачем синхронизировать то что не должно быть синхронизированным?» — что значит синхронизировать? Подразумевается, что я предлагаю хранить состояние одновременно в локальном и глобальном стейте? Нет, только в одном, никакой синхронизации не требуется.

«Мы точно знаем к чему то или иное состояние относится» — нет, если провайдер оборачивает 10 компонентов, то использоваться может лишь в двух, или вообще в глубоко вложенных. Точно знать в данном случае можно только посмотрев реализацию конкретного компонента, проанализировав, какие контексты он импортит. Иерархичность тут абсолютно бесполезна и приводит лишь к путанице.

«Применять какой-то подход к центральному состоянию, и каким будет этот подход? Скорее всего это будет что-то редаксо-подобное» — если это касается меня, это будет что-то реактивное и мутабельное.

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

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


В каких стилях? Можно задавать стили через пропсы, передавая в эти компоненты что нам нужно. Но вообще, в любом случае зачем:

А если бы у них был глобальный стейт типа `elements: [{ left, right, width, height }, ...]`, то это можно было бы сделать динамической формулой и гибко задавать условия взаимного расположения и интеракций.


вообще нужно делать этот стейт глобальным? Почему не обернуть в контекст провайдер компоненты? Хотя в данном случае вообще было бы достаточно сделать компонент со стейтом, внутри которого и использовать эти элементы. Вообще не понятно для чего тут нужна глобальность.

Чтобы наглядней — эта управляющая кнопка далекооо, в компонент Notifications / Gallery ее не поместить.
ну и не помещайте, данный способ не запрещает использовать любой уровень иерархии, включая глобальный.

Так что «чем более независимыми являются компоненты — тем, как правило, лучше» — однозначно нет, не лучше.


Документация Реакта: «Компоненты позволяют разбить интерфейс на независимые части, про которые легко думать в отдельности.»

«Вам не нужно состояние какой-то отдельной формы на глобальном уровне» — в основном как раз нужно.
Зачем? Конечно такие кейсы возможны, но было бы интересно узнать при каких типовых сценариях это может пригодиться.

Нет, только в одном, никакой синхронизации не требуется.
Вы же сами и говорили что: «а вот для фронтенда это — антипаттерн, так как интерфейс в каждый момент времени — цельная система, а синхронизация состояний — ключевой момент.»

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

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

И думать о каждом из них можно как об изолированном блоке, просто не надо думать об иерархии и делать массу импортов.
Да, так можно делать, но на мой взгляд это просто путь к описанному в статье варианту. Единый стор -> много централизованных сторов -> сторы разнесенные по приложению в соответствии с его бизнес логикой. Ментально это более простой способ мыслить о таком подходе если до этого приходилось работать только с централизованными состояниями.

По поводу проблем с оптимизацией — я говорил в комментарии об этом же, что контексты не оптимизированы для использования. И то, что внедрив дополнительный уровень в виде иерархий, можно частично улучшить ситуацию — не вариант.
Контексты в Реакте работают максимально предсказуемым образом, что бы мы, как разработчики, уже сами решали где нам нужно какое либо поведение.

«Вам не нужно состояние какой-то отдельной формы на глобальном уровне» — в основном как раз нужно.
Зачем? Конечно такие кейсы возможны, но было бы интересно узнать при каких типовых сценариях это может пригодиться.

1) Сохранение формы (типа комментов) при рефреше

2) Сохранение предыдущего состояния формы для «создать ещё» или для обновления дефолтных данных
1) во первых, почему вы хотите юзать для этого именно глобальное состояние? Почему бы не юзать состояние более высокого уровня?

Но важно да же не это — если вы хотите сохранять данные юзера, то не надо юзать для этого стейты — они вообще не для этого созданы. Стейт это состояние интерфейса, а не инструмент для сохранения данных.

В данном случае — сохранения данных при рефреше логичнее было бы заюзать localStorage браузера.
«Вам не нужно состояние какой-то отдельной формы на глобальном уровне» — в основном как раз нужно.
Зачем? Конечно такие кейсы возможны, но было бы интересно узнать при каких типовых сценариях это может пригодиться.

3) Для добавления валидаций — если бэк вернул ошибку, что такой email занят, то нужно записать это в список валидаций данного поля
4) При изменении значений в форме можно влиять на другие элементы на странице не событиями form.onChange(values => if (values.fieldValue === 'darkmode') changeWebsiteStyle('darkmode')), так как подобную модель поддерживать сложно и она приводит к «клубку зависимостей», а на глобальном уровне с распространением изменений из стора.

В целом мы скорее зашли в тупик, я говорю — что глобальный стейт позволяет избежать иерархий, лишних вложенностей и импортов, а вы в ответ — почему бы не использовать иерархии и оборачивать только те компоненты, в которых эти стейты используются. Я не согласен с тем, что это проще, и приводил массу сценариев, но, похоже, вас не переубедить, поэтому предлагаю остановить дискуссию и пусть читающие ветку комментариев сами оценивают силу аргументов каждой из сторон.
3) Не понятно что вы хотите записывать в список валидаций, при чем на глобальном уровне — если бэк возвращает ошибку, мы просто отображаем в форме эту ошибку. Логика взаимодействия с бэком — в компоненте или в контексте (в контексте можно писать в том числе и асинхронные запросы).

4) Если вы говорите про форму которая меняет весь стиль сайта — со светлой темы на темную, например — то да, такой стейт должен быть глобальным, провайдер этого стейта будет оборачивать все приложение так как он оказывает влияние на все приложение.

По поводу хранения состояния форм (валидаторы, значения, функции для проверки каждого поля, характеристики полей) я пришел к тому, что глобальный стейт для этого выгоднее локального, но только при реактивной архитектуре. Взаимодействие с бэком вне слоя апи, а напрямую из компонентов приводит к слишком разбросанной и сложно контролируемой логике. «Просто отображать ошибку» — интересно, каким образом вы планируете это сделать, и как планируете не позволять юзеру отправлять поле с некорректными значениями. Но интересно не очень, так как проектировал системы сложных форм десятки раз, и с помощью внешнего обращения к внутреннему стейту компонентов, и параллельной глобальной системой контроля состояния полей непосредственно через DOM, и двухсторонней связкой по системе событий — у этих способов довольно много недостатков, которые можно устранить именно глобальным реактивным состоянием. Попробуйте, должно понравиться.

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

Для бэка подойдет

Да не особо. У бека вполне себе есть единственный источник правды — СУБД. А стейтфул сервера приложений джависты записали в неподдерживаемый говнокод еще во времена Java 5. С переходом на микросерверы у которых СУБД отдельные на микросервис проблема возобновилась, но нормальные люди не переходят на микросервисы на ровном месте, а делают это уже в работающем приложении, где легче выделить относительно самостоятельно работающий компонент.
Тем не менее, бэковые микросервисы не представляют собой единую систему, каждый из них можно переписывать и поддерживать лишь протокол общения. Во фронтенде же все компоненты находятся в едином дом-дереве с общим полем раскладки и пространством стилей, режимом отображения. Мне доводилось создавать системы на изолированных iframe с разных доменов — вот это по факту действительно «микрофронтенды» — но для поддержания общей темы, локализации, размеров, формата, полифиллов и тп пришлось сделать очень толстый АПИ на postMessage для синхронизации. И в то время я как раз и понял, что независимые части в цельной системе — это боль, поэтому сторонился подходов композиции фронта из «как бы независимых» частей, потому что это самообман.

На бэке стейтфул независимые сервисы дают массу преимуществ до того момента, как возникает потребность формировать композитные структуры — тогда добавляются дополнительные слои кеширования, промежуточные сборные базы подготовленных данных и вся обвязка с синхронизацией и т.п., так как делать каждый раз по десятку запросов в разные микросервисы становится чрезвычайно накладно, как бы протоколы общения и внутренняя производительность ни совершенствовались. Но это все решается грамотными архитектурами, сохраняя преимущества в виде независимого цикла развития микросервисов и четкой зоны ответственности. В общем, проблемы есть, но их поменьше, чем если делать подобное на фронте
Данный подход напоминает идею мультисторов в effector, где вместо контекстов мы создаем отдельные сторы и по необходимости можем их объединять.
Чисто на контексте я б не стал делать — вот причины.
Описанные в приведенной ссылки причины не использовать контексты решаются методом описанном в статье, собсно. В комментариях там тоже есть дискуссия на тему использования разноуровневых стейтов.
Да, и согласен с последней мыслью, что это просто инструменты под разные задачи.
Потому я и не стал бы контекстом заменять стейт менеджер.
При желании можно поменять useState на useReducer…
Это дает нам гибкость и уменьшает сложность кода.
Подробнее можно про профит от введения dispatch, action type, Reducer?
Где уменьшение сложности кода, когда вместо прямого вызова функции-действия вводятся 3 дополнительные сущности?
Где увеличение гибкости? Что в архитектуре изменилось в лучшую сторону после введения dispatch, action type, Reducer?

Возможно я не правильно понял и фраза про гибкость и сложность относится к
Одна из крутейших особенностей этого решения заключается в том что мы можем абстрагировать всю логику которую часто применяем для обновления состояния в наш useContext хук:


Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории