blog
blog copied to clipboard
详解 React 的 setState()
详解 React 的 this.setState()
this.setState() 的作用
this.setState()
是 React 中非常重要和基础的一个API
在React中,读取当前状态用是直接访问 this.state 这样的形式的,但是更新状态却是用 this.setState,不是直接在this.state上修改,为什么呢?
//读取状态
const count = this.state.count;
//更新状态
this.setState({count: count + 1});
//无意义
this.state.count = count + 1;
因为更新状态其实是两步操作,一是更改state的值,二是更新渲染对应的UI
this.state说到底只是一个对象,单纯去修改一个对象的值是没有意义的,去驱动UI的更新才是有意义的,想想看,如果只是改了this.state这个对象,但是没有让React组件重新绘制一遍,那有什么用?你可以尝试在代码中直接修改this.state的值,会发现的确能够改变状态,但是却不会引发重新渲染。
所以,需要用一个函数去更改状态,这个函数就是setState,当setState被调用时,能驱动组件的更新过程,引发componentDidUpdate、render等一系列函数的调用。
this.setState() 的用法
setState 有两种调用形式:
void setState(
function|object nextState,
[function callback]
)
- 向 setState 传递对象
this.setState({val: this.state.val+1});
可以向setState()接受对象作为第一个参数
还可以指定第二个参数,是一个更新完上个state状态才会执行的回调函数
this.setState({
val: this.state.val+1
}, () => {
console.log(this.state.val)
});
基本任何用到第二个参数的情况,都可以在 componentDidUpdate 生命周期里定义。
- setState 接受一个函数作为参数
this.setState(callbackFunc)
setState() 不仅能够接受一个对象作为参数,还能够接受一个函数作为参数。函数的参数即为 state 的前一个状态以及 props。
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});
该语法和直接为 setState 传递对象不同,不会 “合并更新”,也就是说,如果更新state的某一个值,而这个值依赖于state的某一个值,可以采用这种方式。这种语法,每次拿到的都是最新的state。
因为 this.props 和 this.state 可能是异步更新的,你不应该依靠它们的值来计算下一个状态。
functional setState 的意义『多次调用 setState 时使用函数作为参数更加保险,React 会将所有的更新组成一个队列,然后按照他们调用的顺序来执行』
并不是所有的场景都适合使用 functional setState 方式。如果需要处理异步的 state 确实会可以获取到之前的 state 并在其基础上进行操作,这样更加的安全。如果需要的只是简单的对象合并,那继续选择对象参数的 setState 方式也是无可厚非。至于纯函数的测试以及分离组件的 action 也是只 functional setState 的加分项。
setState 的“异步”更新和同步更新
setState 方法与包含在其中的执行是一个很复杂的过程,从 React 最初的版本到现在,也有无数次的修改。它的工作除了要更动 this.state 之外,还要负责触发重新渲染,这里面要经过 React 核心 diff 算法,最终才能决定是否要进行重渲染,以及如何渲染。而且为了批次与效能的理由,多个 setState 呼叫有可能在执行过程中还需要被合并,所以它被设计以延时的来进行执行是相当合理的。
根据React官方说明,this. setState() 并不能保证是 “异步” 的,而且异步也与JS执行机制的异步不太一样,准确的说应当是 延迟 执行而已。换句话说,某些情况下,它也是能够马上或者“同步”完成更新的。
所以,并不能保证是马上更新的:
function incrementMultiple() {
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
}
直观上来看,当上面的 incrementMultiple 函数被调用时,组件状态的 count 值被增加了3次,每次增加1,那最后 count 被增加了3。但是,实际上的结果只给 state 增加了1。
但是,你再试试:
componentDidMount() {
document.querySelector('#btn-raw').addEventListener('click', this.onClick);
}
onClick() {
this.setState({count: this.state.count + 1});
console.log('# this.state', this.state);
}
// ......
render() {
console.log('#enter render');
return (
<div>
<div>{this.state.count}
<button id="btn-raw">Increment Raw</button>
</div>
</div>
)
}
你会发现,控制台又打印出了准确的 count 值,进行了同步更新!
深入源码你会发现:
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 this.state,但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由 React 控制的事件处理过程 setState 不会同步更新 this.state。
实际上不仅仅是在react事件系统中是非同步,而是所有通过react生命周期阶段调用的setstate都是非同步的,因为每次setstate都会触发更新阶段的生命周期所以按照正常react用法都是会经过batchingUpdate方法的。这是由于react有一套自定义的事件系统和生命周期流程控制,使用原生事件监听和settimeout这种方式会跳出react这个体系,所以会直接更新this.state。
所以我们得到了很重要的结论:
由 React 控制的事件处理过程 setState 不会同步更新 this.state!
也就是说,
在 React 控制之外的情况, setState 会同步更新 this.state!
- 但大部份的使用情况下,我们都是使用了 React 库中的组件,它们都是 React 库中人造的组件与事件,是处于 React 库的控制之下,比如组件原色 onClick 都是经过 React 包装。在这个情况下,setState 就会以异步的方式执行。
- 然而绕过 React 通过 JavaScript 原生 addEventListener 直接添加的事件处理函数,还有使用 setTimeout/setInterval 等 React 无法掌控的 APIs情况下,就会出现同步更新 state 的情况。