0x01导言
在React的学习中,官方文档 性能优化章节中提到了一个例子产生了一些疑惑,遂刨根问底查看了相关资料来帮助理解。
0x02原文
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});
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}
这是一段bug
代码,并不能达到修改state
并进行render
显示的效果。
下面对代码进行稍微修改,对handleClick
函数进行修改:
handleClick() {
this.setState(state => ({
words: state.words.concat(['marklar'])
}));
}
便可以进行对正常的state
修改,从而调起render
函数进行页面渲染。
0x03 原理分析
官方解释如下:
问题在于
PureComponent
仅仅会对新老this.props.words
的值进行简单的对比。由于代码中WordAdder
的handleClick
方法改变了同一个words
数组,使得新老this.props.words
比较的其实还是同一个数组。即便实际上数组中的单词已经变了,但是比较结果是相同的。可以看到,即便多了新的单词需要被渲染,ListOfWords
却并没有被更新。
简单来说就是虽然进行了setState
,但是同时改变了旧的state
使得在setState
时,新老state
相同,导致不进行render
的调用,页面也没有渲染。
JavaScript中的数组和对象
在JavaScript教程-高级-内存管理中有这样一个例子:
var o = {
a: {
b:2
}
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集
var o2 = o; // o2变量是第二个对“这个对象”的引用
/** 笔者注:试想此时o2与o的关系 (o2是o的引用 并非创建新的内存并赋值) */
o = 1; // 现在,“这个对象”的原始引用o被o2替换了
var oa = o2.a; // 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa
/** 笔者注:试想此时oa与o2.a的关系 (oa是o2.a的引用 并非创建新的内存并赋值) */
o2 = "yo"; // 最初的对象现在已经是零引用了
// 他可以被垃圾回收了
// 然而它的属性a的对象还在被oa引用,所以还不能回收
oa = null; // a属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
在这个例子中,需要注意的是引用和赋值的区别,在JavaScript中var newArr=oldArr
表示的并不是创建新的数组并进行赋值,而是创建newArr
为oldArr
的一个新的引用,这种特性被称为浅拷贝。
试想下面的例子:
var arr = [1,2,3];
var arr2 = arr;
arr2.push(4);
//此时arr是多少?
由于此时arr2
是arr
的一个引用,改变arr2
就会相应的改变arr
,所以此时arr
为[1,2,3,4]
。
0x04解决方案
浅拷贝vs深拷贝
站在巨人的肩膀-参考文章:关于JavaScript的浅拷贝和深拷贝
修改方案
1、 如官方文档例子中使用concat
handleClick() {
this.setState(state => ({
words: state.words.concat(['marklar'])
}));
}
2、 使用JSON
转JSON字符串的方式
handleClick() {
// 使用转JSON字符串的方式
let words = JSON.stringify(this.state.words);
words = JSON.parse(words)
words.push('asdw')
this.setState({ words });
}
3、使用Object.creat
函数
handleClick() {
// 使用Object.creat的方式
let words = Object.create(this.state.words);
words.push('asdw')
this.setState({ words });
}
...更多方式
网友评论