Чтобы начать копаться в неизменяемых данных, мы должны знать, зачем нам нужно знать, что такое неизменяемые данные.
Неизменяемые данные часто применяются в проектах React, одной из причин этого является функциональное программирование.
Следование одной из лучших практик функционального программирования заключается в том, что одна функция не должна иметь побочных эффектов (чистые функции), а это означает, что функция не должна изменять значение, выходящее за пределы области действия функции, и всегда должна возвращать новые данные (например, объект ).
Кроме того, чистые функции должны возвращать один и тот же результат независимо от того, сколько раз вы их запускаете.
Существуют библиотеки, такие как ImmutableJs или Immer, которые помогут нам сделать объект неизменяемым.
В этой статье основное внимание будет уделено неизменяемым данным только с точки зрения нативного JavaScript. Изменяемый объект — это объект, который может быть изменен после инициализации объекта, в то время как неизменяемый объект не является изменяемым, и разработчику не разрешено изменять какое-либо значение объекта после его инициализации.
Изменяемые и неизменяемые данные
Давайте посмотрим на пример ниже:
let mutable = { a: 1 }; let test = mutable; mutable.a = 2; console.log(test); // { a: 2 }
ПРИМЕЧАНИЕ. Вы можете подумать, а как насчет объявления этих переменных с помощью ключевого слова const, которое должно решить проблему. Короткий ответ - нет.
В JavaScript присваивание переменной будет просто ссылаться на объект, и именно поэтому, когда вы изменяете одну из переменных, другая будет изменена вместе. При использовании JavaScript, объект, массив, функция, класс, набор, map — это изменяемые данные. (Но не строка и целое число).
Посмотрите следующий пример неизменяемого типа данных:
let testText = 'hello world'; let anotherTestText = testText; testText = testText.slice(0, 5); console.log(anotherTestText); // 'hello world' console.log(testText); // 'hello'
Глубокое копирование и поверхностное копирование
Чтобы создать новый объект данных, мы должны сначала скопировать исходный объект. Есть несколько способов добиться этого в JavaScript.
- Object.assign
const x = { a: 1 }; const y = Object.assign({}, x); x.a = 11; console.log(y); // { a: 1 }
На этот раз новый объект не изменяется в соответствии с исходным объектом. Пока не горячитесь, см. следующий пример:
const x = { a: 1, b: { c: 2 } }; const y = Object.assign({}, x); x.b.c = 22; console.log(y); // { a: 1, b: { c: 22}}
Данные снова неожиданно изменяются. Почему это происходит? Причина этого в том, что Object.assign предоставляет поверхностную копию, поэтому поддерживает только один уровень ключей и значений. Пока в объекте существует какой-либо объект или массив, эти переменные по-прежнему будут использовать тот же адрес памяти, что и изменяемый объект.
2. Object.freeze
const x = { a: 1, b: { c: 2 } }; const y = Object.freeze(x); x.a = 11; console.log(y); x.b.c = 22; console.log(y); // { a: 1, b: { c: 22}}
Действительно интересно, когда Object.freeze не замораживает объект, не так ли?
3. Уничтожение объекта
const x = { a: 1, b: { c: 2 } }; const y = { ...x }; x.a = 11; console.log(y); x.b.c = 22; console.log(y);
То же самое произойдет и с массивом.
В нативных функциях JavaScript у нас нет ничего, что могло бы обеспечить глубокую копию изменяемых данных.
Как сделать глубокое клонирование
Самый простой способ:
var x = { a: 1, b: 2, c: { d: 3 } }; const y = JSON.parse(JSON.stringify(x)); x.c.d = 100; console.log(y); // { a:1, b:2 , c: { d: 3 }}
Приложение в проекте React
Если мы рассмотрим некоторые сценарии, которые могут быть в реальном проекте React, мы, вероятно, могли бы найти код, похожий на следующий код:
// Following code and output is based on React 18 without StrictMode turned on import { useEffect, useState } from "react"; const processParam = (var1) => { var1.c.d = 100; return var1; }; const SubComponent = ({ object1 }) => <div>{JSON.stringify(object1)}</div>; export default function App() { console.log('<App /> rendered'); const [param] = useState({ a: 1, b: 2, c: { d: 4 }}); return ( <div className="App"> <p>{JSON.stringify(param)}</p> <SubComponent object1={param} /> <p>{JSON.stringify(processParam(param))}</p> </div> ); } // Output: {"a":1,"b":2,"c":{"d":4}} // from {JSON.stringify(param)} {"a":1,"b":2,"c":{"d":100}} // from subcomponent {"a":1,"b":2,"c":{"d":100}} // JSON.stringify(processParam(param))
Это очень плохо, не так ли? Даже если вы выполняете назначение переменной внутри функции processParam, вы все равно будете сталкиваться с этой проблемой, поскольку JavaScript выполняет только поверхностное копирование. Обработка данных без механизма Immutable может привести к большому количеству ненужных повторных рендерингов и непредвиденных проблем.