useState 更新流程

2,135 阅读1分钟

React 触发更新的方式共有 5 种:

ReactDOM.render
this.setState
this.forceUpdate
useState
useReducer

其中,useState (mountState) 是 reducer 参数为 basicStateReducer (basicStateReducer)的 useReducer(mountReducer) 。

所以在更新时,useStateuseReducer 实质调用的是同一个方法 updateReducer

HooksDispatcherOnUpdateInDEV = {
  useReducer: function (reducer, initialArg, init) {
     return updateReducer(reducer, initialArg, init);
  },
  useState: function (initialState) {
    return updateState(initialState);
  },
}

function updateState(initialState) {
  return updateReducer(basicStateReducer);
}

所以,在学习 useState 的更新流程之后,也就知道 useReducer 的更新流程了。

让我们开始。

const App = () => {
  const [a, setA] = useState(1);
  return (
    <>
      <div onClick={() => {
        setA(1);
        setA(2);
        setA(3);
      }}>{a}</div>
		{/*忽略其他无用代码*/}
    </>
  )
}

当点击后,会 setData 三次,整个过程大概是这样的:

setA --> dispatch (dispatchAction) --> scheduleUpdateOnFiber --> ensureRootIsScheduled --> ...Scheduled... --> performSyncWorkOnRoot --> ... -> beginWork --> updateFunctionComponent --> updateFunctionComponent --> renderWithHooks --> Component --> updateReducer --> ...

其中每次 setA 都会经过的方法是:

setA --> dispatch (dispatchAction) --> scheduleUpdateOnFiber --> ensureRootIsScheduled --> ...Scheduled... 

而经过调度后,开始 render 阶段时,三次 setA 已经执行完毕了,数据保存在 hook.queue 里,可以看一下 dispatchAction :

function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  // 忽略无关代码
  const pending = queue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}

注意看下 mountReducermountStatequeue 的赋值和 dispatchAction 的参数:

const queue = (hook.queue = {
  // ...
});

const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));

即可知,update 是保存在 hook.queue 上的。

当调度后,开始 render 时,会经过 renderWithHooks 这个方法,这个方法内会执行 Component 方法,也就是组件本身的 function 方法,组件内部的 useState 会执行,根据条件判断:

if (current !== null && current.memoizedState !== null) {
  ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
}

进而调用到 updateReducer 方法去更新 hook 的值。接下来就到 commit 阶段的 commitMutation 里更新对应的 fiber,最终切换 current 树workInProgress 树,将更新呈现到屏幕上来了。

参考:

updateState

mountState

basicStateReducer

mountReducer

HooksDispatcherOnUpdateInDEV

[dispatchAction](