react爬坑之PureComponent下setstate不触发render中状态变化的问题。
在学习react时出现情况: 明明改变了值, 但是就是不触发render。
下面贴代码
import React, { PureComponent } from 'react';
import CommentInput from '@/components/CommentInput';
import CommentList from '@/components/CommentList';
import styles from './CommentApp.less';
class CommentApp extends PureComponent {
constructor () {
super();
this.state = {
comments:[]
}
}
componentWillMount () {
this._loadComments()
}
_loadComments () {
let comments = localStorage.getItem('comments');
if(comments){
comments = JSON.parse(comments)
this.setState({comments:comments})
}
}
_saveComments (comments) {
localStorage.setItem('comments',JSON.stringify(comments));
}
handleSubmitComment = (comment) =>{
const {comments} = this.state;
if(!comment) return;
if(!comment.username) return alert('请输入用户名');
if(!comment.content) return alert('请输入评论内容');
let temp=[...comments,comment];
this.setState({ comments:temp,});
this._saveComments(temp);
};
handleDeleteComment = (index) => {
//console.log("delete");
const tmp = this.state.comments ;
tmp.splice(index,1) ;
console.log("comments:",tmp);
this.setState({
//comments:[...tmp]
comments:tmp
});
this._saveComments(tmp);
}
render() {
const {comments} = this.state;
console.log("test:",comments);
return (
<div className={styles.wrapper}>
<CommentInput
onSubmit={this.handleSubmitComment}
/>
<CommentList
comments={comments}
onDeleteComment={this.handleDeleteComment}
/>
</div>
)
}
}
export default CommentApp;
handleDeleteComment这个函数的目的是想通过点击删除后 页面会自动显示删除后的列表。在打印tmp发现值是已经打印出来的 但是render中的console.log并没有触发。把
comments:tmp
改成
comments:[...tmp]
后问题解决。
但是用Component代替PureComponent 时,
使用 comments:tmp这种写法又是ok的。所以这里涉及的知识点应该是Component与PureComponent的不同点 以及 深复制与浅复制的一些基础知识点。
- es6解构赋值的相关知识
- 对象的解构赋值
对象的解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
类似 const { id, value } = this.state 其实就是一次浅复制, 我们通常用于在渲染的时候提供一些变化的值,而不需要我们在render中对某个变量进行其他操作。所以在明确只是浅复制的情况下,尽量使用const定义变量,因为const定义的变量不能赋值或更改,这样就避免了后面不小心改变了该变量而引起的问题了。浅复制的好处就是可以有效的节约内存地址,避免不必要的内存浪费。
在ES6中可以利用 … 对对象解构并且对对象中需要深复制的变量进行拷贝。
let obj = {a:1,b:2};
let newObj = {...obj,a:2};//对a深复制
newObj.b = 3;//对b浅复制
console.log(obj);
//{a:1,b:3}
console.log(newObj);
//{a:2,b:3}
在reducer中经常会用到这种方式来更新我们的state,从而达到重新渲染的目的。改变state需要用到 setState() 方法,setState() 方法属于深复制
this.state = {
value: { a: 1 }
}
const { value } = this.state;//浅复制
value.a = 2;
console.log(this.state.value.a);//输出2,但dom不更新
this.setState({ value });//dom更新
这里 value.a = 2 虽然已经改变了state中value的值,但由于是浅复制,新旧value指向的是同一块内存地址,组件更新时(state,props改变)默认只比较新旧对象的内存地址是否一致,如果一致则不更新。同理,如果在reducer中,直接对当前的state进行修改并返回props,相应的调用该props的组件不会更新渲染。
利用 JSON 深拷贝
JSON.parse(JSON.stringify(obj));
- Purecomponent
- PureComponent真正起作用的,只是在一些纯展示组件上
参考文章:https://juejin.im/entry/5934c9bc570c35005b556e1a
https://segmentfault.com/a/1190000014979065
当组件更新时,如果组件的 props 和 state 都没发生改变,render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。
Component和PureComponent有一个不同点
除了为你提供了一个具有浅比较的shouldComponentUpdate方法,PureComponent和Component基本上完全相同。当props或者state改变时,PureComponent将对props和state进行浅比较。另一方面,Component不会比较当前和下个状态的props和state。因此,每当shouldComponentUpdate被调用时,组件默认的会重新渲染。
在React Component的生命周期中,有一个shouldComponentUpdate方法。这个方法默认返回值是true。
这意味着就算没有改变组件的props或者state,也会导致组件的重绘。这就经常导致组件因为不相关数据的改变导致重绘。
React创建了PureComponent组件创建了默认的shouldComponentUpdate行为。这个默认的shouldComponentUpdate行为会一一比较props和state中所有的属性,只有当其中任意一项发生改变是,才会进行重绘。
需要注意的是,PureComponent使用浅比较判断组件是否需要重绘
因此,下面对数据的修改并不会导致重绘(假设Table也是PureComponent)
options.push(new Option())
options.splice(0, 1)
options[i].name = "Hello"
这些例子都是在原对象上进行修改,由于浅比较是比较指针的异同,所以会认为不需要进行重绘。
为了避免出现这些问题,推荐使用immutable.js。immutable.js会在每次对原对象进行添加,删除,修改使返回新的对象实例。任何对数据的修改都会导致数据指针的变化。
- 总结:在对性能要求没有非常强烈的情况下 可以只用Component就可以了。