学习目标:


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调用后)才会被调用

总结:

  1. 对象式setState是函数式setState的简写方式
  2. 使用原则:(1)如果新状态不依赖于原状态==>使用对象方式,反之使用函数方式(2)如果需要获取最新状态数据,则要在第callback函数中读取

关于setState的更新:

我们可以观察到,在调用setState方法更改某值后,页面上该值是更改的,但是在后台打印该值时,却得到一个"旧的状态值",如下所示,这是为什么呢?

react hook typescript项目搭建 react hooks setstate_回调函数


react hook typescript项目搭建 react hooks setstate_回调函数_02

setState本身是一个同步方法,但是它调用完毕后引起的react后续更新状态的动作是异步的,及react状态的更新是异步的,因此如果你想拿到更新完毕后的数值,需要在setState方法的第二个参数及回调函数中获取。

从官网组件状态 的解释中,我们可以看到

  1. setState() 会对一个组件的 state 对象安排一次更新。当 state 改变了,该组件就会重新渲染。
  2. 在 React 中,this.props 和 this.state 都代表着已经被渲染了的值,即当前屏幕上显示的值。不要指望在调用 setState 之后,this.state 会立即映射为新的值,因为调用 setState 是异步的 。
  3. 如果你需要基于当前的 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方法

react hook typescript项目搭建 react hooks setstate_react.js_03


上述流程可总结为如下:

  1. 将setState传入的partialState参数存储在当前组件实例的state暂存队列中。
  2. 判断当前React是否处于批量更新状态,如果是,将当前组件加入待更新的组件队列中。(所有组件使用的是同一套更新机制,当所有组件didmount后,父组件didmount,然后执行更新;更新时会把每个组件的更新合并,每个组件只会触发一次更新的生命周期)
  3. 如果未处于批量更新状态,将批量更新状态标识设置为true,用事务(对应transcation:react的事务机制)再次调用前一步方法,保证当前组件加入到了待更新组件队列中
  4. 调用事务的waper方法,遍历待更新组件队列依次执行更新。
  5. 执行生命周期componentWillReceiveProps。
  6. 将组件的state暂存队列中的state进行合并,获得最终要更新的state对象,并将队列置为空
  7. 执行生命周期componentShouldUpdate,根据返回值判断是否要继续更新。
  8. 执行生命周期componentWillUpdate。
  9. 执行真正的更新,render。
    10.执行生命周期componentDidUpdate。

注意:
1、componentWillUpdate和componentDidUpdate这两个生命周期中不能调用setState。由上面的流程图很容易发现,在它们里面调用setState会造成死循环,导致程序崩溃。
2、推荐使用方式
在调用setState时使用函数传递state值,在回调函数中获取最新更新后的state。