Comments 12
Просто для чистоты эксперимента: если 2500 действий обернуть в batch(() => { ... }), будет быстрее? Оно ведь понятно, что из-за иммутабельности получается O(n^2), но объемы не те, чтобы только асимптотика так разваливала.
Edit и Regular обернуть в React.memo.
А так, да, автор Редукса тоже в нем разочаровался и признал, что Редукс - это медленно и невкусно. В фейсбуке, разумеется, его не используют.
К счастью, современный
React
предоставляет в наше распоряжение другой инструмент, который если не лучше, то уж точно не хужеVuex
Как-то незакончено получается. Не хватает названия или ссылки на инструмент.
Не очень понятна практическая польза сравнения Redux и 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
раз.
в следующий раз, для корректного сравнения, еще добавьте систему реактивности, а то получается сравнение скорости библиотеки против голого нативного кода.
Redux Vs Vuex. Часть 2