React 中与数据相关的属性有: props
、state
和 context
。其中,props
表示父组件传递给子组件的值, state
表示 组件自身的数据对象。context则表示上下文, 因其不太稳定只是个实验性API,不建议大家使用。
直接修改 this.state 的值?
【2018-06-21 更新 】 是否直接修改this.state
的值还取决你的业务需求,假如你在子组件的状态提升中获得一个值的更新,而你的这个值又在state
中的某个对象中,你需要更新这个对象然后再操作如发起axios
请求,这种不需要更新UI内容的情况下可以临时修改state
中的数据。合理使用state的关键是 state
作为一个被React特殊看待的对象,但其实它与普通数据对象无差异,不让直接更新state
的原因是在后续执行setState
过程中会覆盖或者回退之前只修改state
而没有setState
的那部分state
值
在React中,一个组件中要读取当前状态需要访问 this.state
, 而 state
是可以被修改的,因此,大部分初学者都会认为要更新数据直接给 this.state
赋值即可,如下
let sum= this.state.sum;
// 错误的累加, 无意义
this.state.sum = sum + 1;
为什么说是无意义的累加,首先,this.state
实质上就是一个对象,它存放的是组件当前状态的数据值,单纯的去修改数据值然而不将这些值的变化反映给页面这是毫无意义的,因此只修改 this.state
并不能实现页面数据的更新。 React的数据更新,不仅仅需要修改当前状态数据值,还需要驱动UI的更新使组件重新渲染,这个过程就是 this.setState
过程
那是不是做了 this.setState
页面数据就一定会正确更新呢?也未必见得,来看下边这个经典例子:
function incrementMultiple() {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
}
按 javascript
的编程思路,这段代码的执行结果应该是 this.state.count
增加3,然而实际结果并非如此,只增加了1。
官网文档有介绍:
React 为了优化性能,会将多个 setState 的调用合并为一次更新,并且该更新过程是异步的状态更新合并
数据更新 setState() 执行过程
React的 setState
的过程,是将被修改的数据值驱动UI更新反映到页面上的过程,它会引起UI的重新绘制,组件的更新,有如下几个声明周期 lifecircle
:
- shouldComponentUpdate: 组件是否应该被个更新
- componentWillUpdate: 组件即将被更新
- render: 组件更新重新渲染过程
- componentDidUpdate: 组件已经更新完成
这里提到一点,在生命周期中,以 render
为界,无论是挂在还是更新过程,render
之前,this.state
和 props
都是不会发生更新的,直到 render
执行执行完成才得以更新,除非遇到 shouldComponentUpdate
返回 false
, 只有这种情况,即使不执行 render
也能得到数据更新
再回到之前的问题,什么叫状态合并?来看看以下这个例子:
function refUser() {
this.setState({ firstName: 'Jane' });
this.setState({ lastName: 'Chou' });
}
// 等价于
function refUser() {
this.setState({firstName: 'Jane', lastName: 'Chou'});
}
为什么React要做状态合并?
每一次使用 setState
,React都会去调用一次Update生命周期,即上方提到的四个过程,这难免导致不必要的时间和空间浪费,我们知道React的重新渲染过程中,将前后两次的虚拟DOM做比较,并将最终变化数据更新渲染到DOM上,这个过程如果频繁产生,那就会失去React虚拟DOM的优势,状态合并巧妙的规避了这一点。
或许你会问,上例中的状态合并没有异议,那连续增加三次 count
为什么 this.state.count
只增加了1呢?我们来看看 setState
步骤:
如果存在多个 setState 方法,即批量模式下,需要更新state的组件会被push到dirtyComponents中,再执行更新。
我们将前面的代码案例稍微变化如下:
class Plus extends React.Component {
constructor(props) {
super(props);
this.state = {
val: 0
}
}
componentDidMount() {
this.setState({ val: this.state.val + 1 });
console.log(this.state.val); // 返回 0
this.setState({ val: this.state.val + 1 });
console.log(this.state.val); // 返回 0
setTimeout(() => {
this.setState({ val: this.state.val + 1 });
console.log(this.state.val); // 返回 2
this.setState({ val: this.state.val + 1 });
console.log(this.state.val); // 返回 3
}, 0)
}
render() {
return null;
}
}
分析一下这个过程:
-
this.setState
首先会把state
推入pendingState
队列中 - 然后将组件标记为
dirty
- React中有事务的概念,最常见的就是更新事务,如果不在事务中,则会开启一次新的更新事务,更新事务执行的操作就是把组件标记为
dirty
。 - 判断是否处于
batch update
- 是的话,保存组建于
dirtyComponent
中,在事务结束的时候才会通过ReactUpdates.flushBatchedUpdates
方法将所有的临时state merge
并计算出最新的props
及state
,然后将其批量执行,最后再关闭结束事务。 - 不是的话,直接开启一次新的更新事务,在标记为
dirty
之后,直接开始更新组件。因此当setState
执行完毕后,组件就更新完毕了,所以会造成定时器同步更新的情况。
防止循环调用
部分react新手可能和我遇到过相同的问题,即在设置开发过程中突然发现浏览器非常容易卡死,仔细一个控制台才知道有个接口一直在不停的发起递归调用直至电脑内存不足
生命周期中,updateComponent
方法会调用 shouldComponentUpdate
和
componentWillUpdate
方法。因此,不要在 shouldComponentUpdate
和
componentWillUpdate
中调用 setState
。如果在这两个生命周期里调用 setState
,会造成造成循环调用。
网友评论