Недавно я оказался в ситуации, когда столкнулся с ошибкой, вызванной мутировавшим редуктором. Это привело меня в кроличью нору, пока я наконец не смог найти причину.
«Мутирование состояния в React — это анти-паттерн».
Большинство из нас, использующих React, знают об этом и нехотя соглашаются с этим.
Но есть серьезные последствия того, что ваш код React/Redux не является неизменяемым.
Мое путешествие в кроличью нору
В кодовой базе, над которой я работал, была странная ошибка. Компонент, который я создавал, полагался на объект состояния redux. Но обновление этого состояния путем вызова существующего редуктора для обновления состояния не перерисовывало объект.
В течение следующего часа я вырывал свои волосы, пытаясь разобраться в этом. Я глубоко погрузился в теорию, что это должно быть что-то связанное с внутренним устройством моего компонента.
Но потом, спустя долгое время, я решил сменить направление. Что если проблема в глобальном состоянии? Я проверил Redux DevTools, и состояние явно изменилось. Но повторного рендеринга не произошло.
Я начал подозревать, что проверка объектов React не улавливает изменений — но как это может быть, если состояние изменено? Я провел небольшой эксперимент, используя useEffect
.
useEffect(() => {},
console.log('Is this working?')
[myStateValue])
Это не сработало, когда состояние было обновлено, и у меня появился дымящийся пистолет.
Я проверил редуктор, обновляющий это состояние. И тут он уставился на меня, существующий редуктор был мутирован 😱.
state.value = newValue
return {
state
}
Эту ошибку легко допустить.
Этот код появился задолго до того, как я начал работать над кодовой базой. Я ошибочно полагал, что все редукторы должны быть настроены на неизменяемость.
Но я понял, что это ошибка, которую легко допустить. Сложность в том, что технически код работает — но с огромными оговорками (ошибка с перерисовкой, с которой я столкнулся, — одна из них).
Это может происходить потому, что люди забывают, или во время рефакторинга для упрощения кода. Установка значения напрямую — это то, к чему привыкли многие программисты. К моему ужасу, после просмотра существующего кода в нем было еще больше мутировавших состояний.
Как должен был выглядеть код редуктора
return {
...state,
value: newValue
}
Есть небольшая разница в коде, но огромная разница в том, как это проверяется React/Redux.
Почему вы никогда не должны мутировать состояние
Во-первых, Redux предостерегает от мутирования состояния. Есть много, много причин, почему. Но главная из них заключается в том, что Redux использует неглубокую проверку равенства, а НЕ глубокую проверку равенства.
Во-вторых, в React вообще не следует изменять состояние объекта. React использует [.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)
проверку ссылок. Это означает, что он проверяет, является ли ссылка на объект одинаковой в памяти, а НЕ имеет ли она различные вложенные значения. React Hooks делает очень хорошую работу по предотвращению этого, но с редукторами Redux эта работа остается за вами.
// The problematic code above is like coding:
const [value, setValue] = useState('a')
state.value = 'b'
// instead of coding:
setValue('b')
В моем случае мне потребовался час, чтобы найти проблему, но меньше минуты, чтобы ее устранить.
Вывод
Никогда не мутируйте состояние React и Redux!
Если вы обнаружили старый код, который мутирует — разработайте план быстрого решения этой проблемы, иначе это может создать кошмар для отладки.