学习目标:
setState
学习内容:
学习产出:
setState
setState更新状态的两种写法。
(1)对象式的setState
setState(stateChange, [callback])
stateChange为 状态改变对象(该对象可以提现状态的更改)
callback是可选回调函数,它在状态更新完毕、界面更新完毕后(render调用后)才会被调用
(2)函数式setState
setState(updater, [callback])
updater为返回 stateChange对象 的函数(即返回的是改变后的对象)
updater可以接收到state和props(注意不是this.state)
callback是可选回调函数,它在状态更新完毕、界面更新完毕后(render调用后)才会被调用
总结:
- 对象式setState是函数式setState的简写方式
- 使用原则:(1)如果新状态不依赖于原状态==>使用对象方式,反之使用函数方式(2)如果需要获取最新状态数据,则要在第callback函数中读取
关于setState的更新:
我们可以观察到,在调用setState方法更改某值后,页面上该值是更改的,但是在后台打印该值时,却得到一个"旧的状态值",如下所示,这是为什么呢?
setState本身是一个同步方法,但是它调用完毕后引起的react后续更新状态的动作是异步的,及react状态的更新是异步的,因此如果你想拿到更新完毕后的数值,需要在setState方法的第二个参数及回调函数中获取。
从官网组件状态 的解释中,我们可以看到
- setState() 会对一个组件的 state 对象安排一次更新。当 state 改变了,该组件就会重新渲染。
- 在 React 中,this.props 和 this.state 都代表着已经被渲染了的值,即当前屏幕上显示的值。不要指望在调用 setState 之后,this.state 会立即映射为新的值,因为调用 setState 是异步的 。
- 如果你需要基于当前的 state 来计算出新的值,那你应该传递一个函数,而不是一个对象。给 setState 传递一个函数,而不是一个对象,就可以确保每次的调用都是使用最新版的 state
incrementCount() {
this.setState((state) => {
// 重要:在更新的时候读取 `state`,而不是 `this.state`。
return {count: state.count + 1}
});
}
handleSomething() {
// 假设 `this.state.count` 从 0 开始。
this.incrementCount();
this.incrementCount();
this.incrementCount();
// 如果你现在在这里读取 `this.state.count`,它还是会为 0。
// 但是,当 React 重新渲染该组件时,它会变为 3。
}
再来看下react源码中如何管理state的更新:useState的源码
type BasicStateAction<S> = (S => S) | S;
useState<S>(initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return rerenderState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
function rerenderState<S>(initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {
return rerenderReducer(basicStateReducer, (initialState: any));
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
// 如果action是一个函数的时候,则返回执行该函数的结果,否则返回该函数
return typeof action === 'function' ? action(state) : action;
}
配合下图食用~其中
partialState:setState传入的第一个参数,对象或函数
_pendingStateQueue:当前组件等待执行更新的state队列
isBatchingUpdates:react用于标识当前是否处于批量更新状态,所有组件公用
dirtyComponent:当前所有处于待更新状态的组件队列
transcation:react的事务机制,在被事务调用的方法外包装n个waper对象,并一次执行:waper.init、被调用方法、waper.close
FLUSH_BATCHED_UPDATES:用于执行更新的waper,只有一个close方法
上述流程可总结为如下:
- 将setState传入的partialState参数存储在当前组件实例的state暂存队列中。
- 判断当前React是否处于批量更新状态,如果是,将当前组件加入待更新的组件队列中。(所有组件使用的是同一套更新机制,当所有组件didmount后,父组件didmount,然后执行更新;更新时会把每个组件的更新合并,每个组件只会触发一次更新的生命周期)
- 如果未处于批量更新状态,将批量更新状态标识设置为true,用事务(对应transcation:react的事务机制)再次调用前一步方法,保证当前组件加入到了待更新组件队列中。
- 调用事务的waper方法,遍历待更新组件队列依次执行更新。
- 执行生命周期componentWillReceiveProps。
- 将组件的state暂存队列中的state进行合并,获得最终要更新的state对象,并将队列置为空
- 执行生命周期componentShouldUpdate,根据返回值判断是否要继续更新。
- 执行生命周期componentWillUpdate。
- 执行真正的更新,render。
10.执行生命周期componentDidUpdate。
注意:
1、componentWillUpdate和componentDidUpdate这两个生命周期中不能调用setState。由上面的流程图很容易发现,在它们里面调用setState会造成死循环,导致程序崩溃。
2、推荐使用方式
在调用setState时使用函数传递state值,在回调函数中获取最新更新后的state。