React setState() 的原理解析
setState 的原理机制解析
我们本章节主要是要来分析一下React
中常见的setState
方法,熟悉React
的小伙伴应该都知道,该方法通常用于改变组件状态并用新的state
去更新组件。但是,这个方法在很多地方的表现总是与我们的预期不符,先来看几个案例。
常见案例
class Root extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
let that = this;
that.setState({ count: that.state.count + 1 });
console.log(that.state.count); // 打印出 0
that.setState({ count: that.state.count + 1 });
console.log(that.state.count); // 打印出 0
setTimeout(function(){
that.setState({ count: that.state.count + 1 });
console.log(that.state.count); // 打印出 2
}, 0);
setTimeout(function(){
that.setState({ count: that.state.count + 1 });
console.log(that.state.count); // 打印出 3
}, 0);
}
render() {
return (
<h1>{this.state.count}</h1>
)
}
}
这个案例的结果确实让人非常意外,打印出 0
,0
,2
,3
。为什么没有按照我们预期的1
,2
,3
,4
往下进行这样的打印方式呢?我们接着往下看:
在了解setState
之前,我们先来简单了解下 React 一个包装结构: Transaction
:
事务 (Transaction
):
它呢,是 React 中的一个调用结构,用于包装一个方法,结构为: initialize
- perform(thatthod)
- close
。通过事务,可以统一管理一个方法的开始与结束;处于事务流中,表示进程正在执行一些操作;
设置状态值 setState
:
在 React
中setState
用于修改状态,更新视图。它具有以下特点:
异步与同步: setState
并不是单纯的异步或同步,这其实与调用时的环境相关:
- 在 合成事件 和 生命周期钩子(除
componentDidUpdate
) 中,setState是"异步"的;
原因:
因为在setState
的实现中,有一个判断: 当更新策略正在事务流的执行中时,该组件更新会被推入dirtyComponents
队列中等待执行;否则,开始执行batchedUpdates
队列更新;
- 在生命周期钩子调用中,更新策略都处于更新之前,组件仍处于事务流中,而
componentDidUpdate
是在更新之后,此时组件已经不在事务流中了,因此则会同步执行; - 在合成事件中,
React
是基于 事务流完成的事件委托机制 实现,也是处于事务流中;
- 问题: 无法在
setState
后马上从this.state
上获取更新后的值。- 解决: 如果需要马上同步去获取新值,
setState
其实是可以传入第二个参数的。setState(updater, callback)
,在回调中即可获取最新值;
- 解决: 如果需要马上同步去获取新值,
setState({
index: 1
}}, function(){
console.log(this.state.index);
})
* 在钩子函数中体现:
componentDidUpdate(){
console.log(this.state.index);
}
-
在 原生事件 和
setTithatout
中,setState
是同步的,可以马上获取更新后的值;- 原因: 原生事件是浏览器本身的实现,与事务流无关,自然是同步;而
setTithatout
是放置于定时器线程中延后执行,此时事务流已结束,因此也是同步;
- 原因: 原生事件是浏览器本身的实现,与事务流无关,自然是同步;而
-
批量更新: 在 合成事件 和 生命周期钩子 中,
setState
更新队列时,存储的是 合并状态(Object.assign
)。因此前面设置的key
值会被后面所覆盖,最终只会执行一次更新;如下图所示:
- 函数式: 由于
Fiber
及 合并 的问题,官方推荐可以传入 函数 的形式。setState(fn)
,在fn中返回新的state
对象即可,例如this.setState((state, props) => newState)
;使用函数式,可以用于避免setState
的批量更新的逻辑,传入的函数将会被 顺序调用;
class Com extends Component{
constructor(props){
super(props);
this.state = { index: 0 };
this.add = this.add.bind(this);
}
add(){
this.setState(prevState => {
return {index: prevState.index + 1};
});
this.setState(prevState => {
return {index: prevState.index + 1};
});
}
}
注意事项:
-
setState
合并,在 合成事件 和 生命周期钩子 中多次连续调用会被优化为一次; -
当组件已被销毁,如果再次调用
setState
,React
会报错警告,通常有两种解决办法:- 将数据挂载到外部,通过
props
传入,如放到Redux
或 父级中; - 在组件内部维护一个状态量
(isUnmounted)
,componentWillUnmount
中标记为true
,在setState
前进行判断;
- 将数据挂载到外部,通过
上一篇: 搞笑动漫趣图合辑
下一篇: 搞笑动漫图片--漫画图片