常规情况
在同一个方法中多次 setState 是会被合并的,并且对相同属性的设置只保留最后一次的设置;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import React from "react";
export class Test extends React.Component { constructor(props) { super(props); this.state = { count: 0, }; } componentWillMount() { let me = this; me.setState({ count: me.state.count + 2, }); me.setState({ count: me.state.count + 1, }); } componentDidMount() { let me = this; me.setState({ count: me.state.count + 2, }); me.setState({ count: me.state.count + 1, }); }
onClick() { let me = this; me.setState({ count: me.state.count + 1, }); me.setState({ count: me.state.count + 1, }); }
render() { console.log(this.state.count); console.log("1111111111111111111111111111111111111111111"); return ( <div> <h1>{this.state.count}</h1> <input type="button" value="点击我" onClick={this.onClick.bind(this)} /> <br /> <br /> </div> ); } }
|
上述执行过程如下:
willmount
中的setState
会合并成一次执行,count
只会保留最后一次的设置,前面的放弃,所以willmount
之后是1
,并不是3
;并且在render
之前执行,不会引起新的render
- render 之后执行 didMount,setState 做同样的处理,这是
count
是2
,并且引起新的 render
- 点击按钮,
setState
做同样处理,count
是3
,引起新的render
###定时器中的 setState
定时器中的 setState,每次都会引起新的 render,即使是同一个定时器中的多次 setState
代码更改成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| componentWillMount() { let me = this; setTimeout(() => { me.setState({ count: me.state.count + 1 }); me.setState({ count: me.state.count + 1 }); }, 0); }
componentDidMount() { let me = this; setTimeout(() => { me.setState({ count: me.state.count + 1 }); me.setState({ count: me.state.count + 1 }); }, 0); }
onClickTime() { let me = this; setTimeout(() => { me.setState({ count: me.state.count + 1 }); me.setState({ count: me.state.count + 1 }); }, 0); }
|
上述代码,每次setState
都会引发新的 render,需要深入了解的可以查查setState
的原理,简单理解是定时器中的setState
没走react
的事物机制,执行时批量更新没被设置true
,所以每次都直接 render 了。
原生事件中的 setState
- 在按钮原生事件中定义的
setState
,和定时器效果一样,每次setState
都会引起新的render
- react 事件是合并的成一次 render 的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| componentDidMount() { this.button.addEventListener('click', this.onClick.bind(this, '原生事件'), false); }
onClick(info) { console.log(info); this.setState({ count: ++count }); this.setState({ count: ++count }); }
render() { console.log(this.state.count); return <div> <input type="button" ref={input => this.button = input} onClick={this.onClick.bind(this, 'React事件')} value="生成计时器" /> <div>Count:{this.state.count}</div> </div> }
|
点击按钮,先执行原生事件,再执行 react 事件,但是原生事件会触发两次 render,react 事件触发一次。
总结
上述是我对 setState 的理解,抛砖引玉,希望帮助大家有方向的去了解 react 原理机制。刚开始接触,很多同学想深入了解,但可能不知道从何入手,这也是我遇到过的困扰,所以现在分享出来,希望能帮助大家少走弯路,更快的、更有准针对性的去研究学习 React。
以下为补充内容
原因是什么?
原因是:执行上下文发生了变化。
在事件执行的时候,当前上下文执行的是setTimeout
函数,但当执行setTimeout
函数的回调时,原来的上下文已经结束了,回调的上下文变成了 window,所以依据的批量更新属性isBatchingUpdates
没有被设置成true
的过程,因此始终是false
,因此setState
就同步执行了。
回调不会触发 react 的批量更新机制
其实在回调函数中,setState 是不会触发批量更新机制的,无论是 promise,ajax,setTimeout 回调等等,同时设置多次 setState,每个 setState 都会单独执行并 render,因为上下文发生了变化。
下面是验证 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| onClickBtn = () => {
fetch("/api/getlist") .then(response => { return response.json(); }) .then(data => { console.log(JSON.stringify(data)); this.setState({ count: this.state.count + 1 }); console.log(this.state.count); this.setState({ count: this.state.count + 1 }); console.log(this.state.count); }); };
|
生命周期和事件中多次 setState 的区别
在写 demo 时发现,虽然didMount
中的多次setState
会被合并,符合正常的规律,但是通过调试发现,在didMount
中isBatchingUpdates
始终是false
,而事件调用触发的setState
,isBatchingUpdates
则是true
。