参照文档:
react 更新已渲染的元素React 元素是不可变对象。 MDN # Object.freeze()
1.基本数据类型和引用数据类型
在js中,所有的基本数据类型(Undefined, Null, Boolean, Number, String, Symbol)都是不可变的,但是对象(Object,Array,Function)是可变的。 基本数据类型值存在栈中,而 object 存在栈中的是地址值(对堆中地址的引用)。下面obj1和obj2指向同一个地址,所以修改obj2的某个属性,obj1也同步改变,这就是所谓可变的对象。
var a = 1;
var b = a;
b = 2;
console.log('a', a)
const obj1 = {
name: 'bwf',
age: 18,
job: {
name: 'web',
salary: '15k'
}
}
const obj2 = obj1;
obj2.name = 'wmy'
console.log('obj1', obj1)
2那么如果创建不可变的对象呢?
第一次尝试 const !
注意:const实际上保证的,并不是变量的值不得改动,而是变量指向的内存地址不得改动。所以上面 的obj1虽然是用const定义的,但是内容还是被改变了
继续尝试:Object.freeze()
Object.freeze(obj1);
obj2.age = 30;
console.log('obj1', obj1) //obj1的age没有被改变
obj2.job = {
name: 'java',
salary: '20k'
}
console.log('obj1', obj1) //obj1的job没有被改变 对象的第一层拷贝
obj2.job.name = 'web'
console.log('obj1', obj1) //obj1的job的name还是被改变le 对象的深层次拷贝
我们可以发现仍然可以更改嵌套对象。
最终解决方案 为了使对象完全不可变,我们还需要freeze()所有的嵌套对象。例如(方法来自MDN )
function deepFreeze(object) {
// Retrieve the property names defined on object
const propNames = Object.getOwnPropertyNames(object);
// Freeze properties before freezing self
for (const name of propNames) {
const value = object[name];
if (value && typeof value === "object") {
deepFreeze(value);
}
}
return Object.freeze(object);
}
3 react和redux中的数据都是不可变对象
不可变意味着不能直接修改原数据,即state的值。如果需要修改,需要对原数据进行拷贝
react
数组
bad :下面示例是不会触发视图更新的,因为push方法虽然改变了原数组,但是是同一个引用,react的更新机制是浅比较,如果前后2次的地址相同,它会认为数据没有更新,从而不会更新视图。
const addTodo = (content)=>{
todoList.push(
{
id:id++,
content
}
)
setTodoList(todoList)
}
创建不可变对象的方式一:concat
let id = 0;
const todoListInitial =[
{
id,
content:'吃饭'
},
]
const [todoList,setTodoList] = useState(todoListInitial)
const addTodo = (content)=>{
const newTodoList = todoList.concat()
newTodoList.push(
{
id:id++,
content
}
)
setTodoList(newTodoList)
}
创建不可变对象的方式一:扩展运算符
let id = 0;
const todoListInitial =[
{
id,
content:'吃饭'
},
]
const [todoList,setTodoList] = useState(todoListInitial)
const addTodo = (content)=>{
todoList.push(
{
id:id++,
content
}
)
setTodoList([...todoList])
}
对象
const Person = ()=>{
const p = {
name:'bwf',
age:18
}
const [bwf,setBwf] = useState(p);
const addSex = ()=>{
// 错误方式:下面这样写因为是同一个引用地址,不会触发视图更新
bwf.sex= '女'
setBwf(bwf)
// 正确方式一 :对象扩展运算符
bwf.sex= '女'
setBwf({...bwf});
// or
bwf.sex= '女'
setBwf({...bwf,sex:'女'})
// 正确方式二 Object.assign
const newBwf = Object.assign({},bwf)
newBwf.sex= '女'
setBwf(newBwf);
}
return (
<div>
{Object.keys(bwf).map(item=><li>{bwf[item]}</li>)}
<button onClick={addSex}>点我添加一个性别属性</button>
</div>
)
}
class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
}
class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// 这部分代码很糟,而且还有 bug:数据改了,视图不更新
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
我修改的方式
// this.setState({
// words:words.concat()
// });
this.setState({
words:[...words]
});
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}
避免该bug最简单的方式是避免更改你正用于 props 或 state 的值。例如,上面 handleClick 方法可以用 concat 重写:
下面使用函数式更新state,我可以借鉴下
handleClick() {
this.setState(state => ({
words: state.words.concat(['marklar'])
}));
}
handleClick() {
this.setState(state => ({
words: [...state.words, 'marklar'],
}));
};
又例如,我们有一个叫做 colormap 的对象。我们希望写一个方法来将 colormap.right 设置为 'blue'。我们可以这么写:
function updateColorMap(colormap) {
colormap.right = 'blue';
}
为了不改变原本的对象,我们可以使用 Object.assign 方法:
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
或者对象扩展属性
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
当处理深层嵌套对象时,以 immutable (不可变)的方式更新它们令人费解。如遇到此类问题,请参阅 Immer 或 immutability-helper。这些库会帮助你编写高可读性的代码,且不会失去 immutability (不可变性)带来的好处。