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

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

Просто для чистоты эксперимента: если 2500 действий обернуть в batch(() => { ... }), будет быстрее? Оно ведь понятно, что из-за иммутабельности получается O(n^2), но объемы не те, чтобы только асимптотика так разваливала.

Edit и Regular обернуть в React.memo.

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

К счастью, современный React предоставляет в наше распоряжение другой инструмент, который если не лучше, то уж точно не хуже Vuex

Как-то незакончено получается. Не хватает названия или ссылки на инструмент.

я имел ввиду useContext + useReducer) + если обернуть редуктор в produce() из immer (и вместо state работать с draft), то можно мутировать состояние напрямую

Не очень понятна практическая польза сравнения Redux и Vuex. Это как сравнивать, кто сильнее: акула или медведь) У них всё равно разный ареал обитания и заменить один на другой не получится.

Очень странное заявление. А как тогда у автора получилось заменить одно на другое?

Правильно подметил. Но все таки это говорит о том что vuex как инструмент практичнее.

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

Заменил в изначальном Redux варианте циклические вызовы добавления на множественное добавление https://codesandbox.io/s/redux-todo-forked-resdk и вышло 7ms на каждое из 25 созданий в среднем и 80мс на общее обновление. В дев режиме со всеми дефолтными мидлварами тулкита и прочим.

Так что дело точно не в EntityAdapter. Причем я специально не использовал из адаптера функции addMany и updateMany.

Я просмотрел статью очень вскользь. На мой взгляд в статье очень много неточностей, нюансов и не правильные выводы.

Архитектура Flux (в Redux) предполагает следующее:
состояние изменяется только с помощью чистых функций — операций (actions);

actions - это не чистые функции, а обычные объекты. И функции которые создают события (action creators) так же не обязательно должны быть чистыми. Чистыми функциями обязаны быть только reducers и selectors. И в этом очень много смысла. Я могу расписывать часами то, насколько сильно чистые функции упрощают жизнь, если понимать их преимущества.

Наиболее показательные и стабильные результаты удалось получить с помощью записи 2500 задач и их последующего обновления.

И делается это с помощью вызова dispatch 2500 раз. Это абсолютно не соответсвует идеям redux и здравому смыслу. Когда нужно добавить или отредактировать 2500 элементов, это должно делаться с помощью одной тракзакции. Об этом написано в strongly recommended правилах в redux style-guide:
https://redux.js.org/style-guide/style-guide#avoid-dispatching-many-actions-sequentially

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

Я почти на 100% уверен, что проблема с производетельностью является производной нарушения правила выше и особенностями работы immer который redux-toolkit использует под коробкой. Дело в том, что immer работает очень медленно, если вызывать его в цикле как это делается в примерах статьи.
Из документации immer:

for (let x of y) produce(base, d => d.push(x)) is exponentially slower than produce(base, d => { for (let x of y) d.push(x)})

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

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

useSelector из react-redux работает таким образом, что если данные на которые ссылается селектор не изменились, компонент не обновлялся и команда react-redux приложила не мало усилий, чтобы сделать поведение useSelector именно таким и исправить баги "Stale Props" и "Zombie Children".

команда react-redux приложила не мало усилий, чтобы ... исправить баги "Stale Props" и "Zombie Children".

насколько я знаю (где-то читал), те усилия в основном свелись к навешиванию try...catch вокруг вызова селектора.

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

В общем, после чтения этого кода видно, что есть куча нюансов которые команда redux уже решили за нас. И я не вижу смысла повторять это все для того, чтобы перейти на useReducer + useContext. К тому же в этом подходе не закрыты еще 2 вопроса - асинхронность и интеграция с devtools. Они конечно разрешими, вот только зачем их решать самому если в итоге получится тот же redux?

Другое дело если бы был подход, который максимально сохраняет преимущества redux и добавляет много новых.

И я не вижу смысла повторять это все для того, чтобы перейти на useReducer + useContext.

Да это понятно. О том, что контекст нихрена не стейт-манагер, уже сказано миллион раз. Просто благодаря хукам в Реакте стало легко работать с контекстом, и сотни велосипедистов-стейтманагерщиков ломанулись в эту пропасть :)

Таким образом, по сравнению с Vuex мы получаем прирост производительности: для записи — в 4-5 раз, для обновления (внимание!) — в 100-150 раз.

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

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