React 中与数据相关的属性有: props,state 和 context. 其中, props 表示父组件传递给子组件的值, state 表示 组件自身的数据对象. context 则表示上下文, 因其不太稳定只是个实验性 API, 不建议大家使用.
直接修改 this.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 的调用合并为一次更新, 并且该更新过程是异步的状态更新合并 http://www.CSS88.com/react/docs/state-and-lifecycle.html
数据更新 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 , 会造成造成循环调用.
来源: http://www.jianshu.com/p/54a8a0688197