Есть два подхода к изменению данных
- Перезаписать память "на месте" - стереть старое значение и установить новое. Такой способ называется "мутация данных".
- Заменять данные новой копией, которая содержит изменения. Старая версия не удаляется, при необходимости ее удалит сборщик мусора.
Неизменяемые данные - данные, которые после своего создания никогда не меняются в памяти. Вместо этого создадим новую копию данных с необходимыми изменениями.
Механика копирования в JavaScript
Примитивные значения
Составные значения - Object, array, etc
Последствия мутации данных
Возможность мутации крадет у нас гарантию, что данные будут теми, которые мы объявили. Наихудший сценарий, когда один объект используется в разных частях кода, тогда мутация объекта в одном месте может привести к ошибкам в другом. Подобные ошибки сложно поймать, так как причина проблемы лежит за пределами фактической ошибки. Отдельная часть кода может быть нормальный, но данные приобрели неправильную форму и все сломалось.
Другие ситуации
- Мутация может быть причиной плохих сайд эффектов
- Мутация не хранит историю изменений, данные в памяти перезаписываются новыми
- Мутация может быть источником непредсказуемого состояния
Высказывание Rich Hickey
If guarantee about value not exists, then our object is only a pointer to the place in the memory, nothing more
Если у нас нет гарантии, что определенное значение существует, тогда наш объект всего лишь ссылка на область памяти, ничего больше (то есть мусор)
Неизменяемые данные в JavaScript
Конечно мы можем мутировать данные в JavaScript 😈, но лучше этого не делать, тогда мы получим более надежный код и предсказуемое состояние
Используем spread object || spread array оператор
Обратите внимание, spread оператор при расширении объекта, в котором находятся другие объекты - переиспользует ссылки на последние. Но такая оптимизация не "ломает" нашу неизменяемость, так как при мутации внутреннего объекта moreCarsObj.first, carsObj.first остается неизменным.
Еще есть Object.assign для объектов и Array.concat для массивов, но в современных приложениях чаще используется spread. Но когда вложенность данных становится глубокой, обычно в проект внедряют специальные библиотеки - immutable.js, mori.
React + неизменяемые данные
Немного повторим: компонент React обновляется только в случаях:
- Вызов this.setState()
- Вызов this.forceUpdate()
- Вызов render() родительского компонента
Обратите внимание на формулировку - она точно отражает суть. Не при изменении state или props компонента, а именно после вызовов this.setState(), this.forceUpdate() или render() родителя.
Можете посмотреть proof на codePen.
Комментарий к примеру codePen
Нажимая на кнопку Rerender мы мутируем state. Поле index данного state передается как props компоненту Child. Если бы Child обновлялся при изменении своих props (а они обновляются за счет мутации state), то он вызвал бы render(), но этого не происходит. Child обновится только после вызова setState() и можете заметить, что мутирующие добавления +1 будут учтены.
PROPS
В React 0.12 объект props был изменяемым и это приводило к некоторым проблемам. В новых версиях библиотеки при попытке мутировать props мы увидим ошибку и приложение "упадет"
STATE
Согласно документации в React со state принято работать как с неизменяемыми данными, хотя технически мы можем мутировать его.
Проблемы при мутации state
- React компонент не вызовет render() ---> UI не обновится
- Ближайший вызов setState() может перезаписать предыдущие "мутирующие" изменения ---> потеряем желаемый state
- Оптимизирующие проверки в chouldComponentUpdate / PureComponent всегда будут возвращать ложные значения
shoulComponentUpdate / PureComponent
Метод shouldComponentUpdate вызывается перед каждым потенциальным рендером и в качестве аргументов принимает nextProps и nextState. Мы можем сравнить текущие this.props и this.state с новыми nextProps и nextState. Если ничего не изменилось, вернем false
и render() не вызовется, а значит не вызовется и у всех дочерних компонентов. Профит ? - почти...
Если мы мутировали state, то новых ссылок создано не будет, так что nextState.value === this.state.value всегда вернет true (хотя данные были мутированы - изменены). Наша проверка сломается и компонент не обновится.
Заключение
Пишите код в рамках идеи неизменяемых данных. Каждый раз, когда меняются данные, создавайте новую ссылку.
Источники
Доументация React
The state of immutability
Immutability in React
Основы производительности React-приложений
Immutability in React and Redux
Be wary of that shallow equal