class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log 0
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log 0
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log 2
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log 3
}, 0);
}
render() {
return null;
}
};
// 假如
componentDidMount(){
for ( let i = 0; i < 100; i++ ) {
this.setState( { num: this.state.num + 1 } );
}
}
组件渲染的结果是1,·
且在控制台中输出了100次0
,说明每个循环中,拿到的state仍然是更新之前的。
react 如何优化以上?
setState接收的参数还可以是
一个函数
,在这个函数中可以拿先前的状态,并通过这个函数的返回值得到下一个状态
componentDidMount() {
for ( let i = 0; i < 100; i++ ) {
this.setState( prevState => {
console.log( prevState.num );
return {
num: prevState.num + 1
}
} );
}
}
合成事件中的setState
合成事件,react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,
像在jsx中常见的onClick、onChange这些都是合成事件
合成事件中也有batchedUpdates方法,是通过同样的事务完成的
class App extends Component {
state = { val: 0 }
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新前的val --> 0
}
render() {
return (
<div onClick={this.increment}>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
生命周期函数中的setState
整个生命周期中就是一个事物操作,所以标识位isBatchingUpdates = true,所以流程到了enqueueUpdate()时,实例对象都会加入到dirtyComponents 数组中
class App extends Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的还是更新前的值 --> 0
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
其实还是和合成事件一样,
componentDidmount 执行的时候,react内部并没有更新
,执行完componentDidmount 后才去 commitUpdateQueue
更新。这就导致你在componentDidmount 中 setState 完去console.log拿的结果还是更新前的值
。
原生事件中的setState
原生事件是指非react合成事件,
原生自带的事件监听 addEventListener ,或者也可以用原生js、jq直接 document.querySelector().onclick
这种绑定事件的形式都属于原生事件
原生事件绑定不会通过合成事件的方式处理
,自然也不会进入更新事务的处理流程。setTimeout也一样,在setTimeout回调执行时已经完成了原更新组件流程
,不会放入dirtyComponent进行异步更新,其结果自然是同步的
。
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
批量更新
在 setState 的时候react内部会创建一个
updateQueue ,通过 firstUpdate 、 lastUpdate 、 lastUpdate.next
去维护一个更新的队列,在最终的 performWork 中,相同的key会被覆盖,只会对最后一次的 setState 进行更新
class App extends Component {
state = { val: 0 }
batchUpdates = () => {
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
}
render() {
return (
<div onClick={this.batchUpdates}>
{`Counter is ${this.state.val}`} // 1
</div>
)
}
}
在官方的描述中,setState操作并不保证是同步的,也可以认为是异步的。
React在setState之后,会经对state进行diff,判断是否有改变,然后去diff dom决定是否要更新UI。如果这一系列过程立刻发生在每一个setState之后,就可能会有性能问题。
在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果
生命周期函数里面可以setState 吗? 什么时候setState 合适?
-
在 componentWillMount中执行 setState 是无意义的
,应该将这里的 setState 放到初始化 this.state 的地方去(如 constructor)直接作为 state 的初始值。
原因是:组件只挂在一次,在componentWillMount里setState 会但是仅会更新state一次,而且会和 constructor 里的初始化 state 合并执行,因此这是无意义的 setState。
- 禁止在 shouldComponentUpdate 和 componentWillUpdate中调用setState,
这会造成循环调用,直至耗光浏览器内存后崩溃
。
shouldComponentUpdate
或者componentWillUpdate
里调用 setState 会再次触发这两个函数,然后在两个函数又触发了 setState,然后再次触发这两个函数
在 componentDidUpdate 中执行 setState 会导致组件刚刚完成更新,又要再更新一次,连续渲染两遍(和在 componentDidMount 中执行 setState 类似)
在
componentWillReceiveProps
中可以 setState,不会造成二次渲染。
由于只有 props 的变化才会触发componentWillReceiveProps
事件,因为在这个事件里 setState 不会造成不停触发组件更新的死循环,可以放心在这个函数里 setState