react 16 Hooks渲染流程

Posted dh-dh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react 16 Hooks渲染流程相关的知识,希望对你有一定的参考价值。

useState

react对useState进行了封装,调用了mountState。

function useState<S>(
    initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] 
    currentHookNameInDev = 'useState';
    mountHookTypesDev();
    const prevDispatcher = ReactCurrentDispatcher.current;
    ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
    try 
        return mountState(initialState);
     finally 
        ReactCurrentDispatcher.current = prevDispatcher;
    

mountState

如果initialState是函数还可以执行。
生成一个dispatch方法,通过闭包绑定当前states。
把初始值存到memoizedState上。这个memoizedState绑定到fiber树上。用来存储state。

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] 
    // 把hooks加入queue,实际上是为了保证执行顺序。
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') 
    initialState = initialState();
  
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = 
    last: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  );
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    // Flow doesn't know this is non-null, but we do.
    ((currentlyRenderingFiber: any): Fiber),
    queue,
  ): any));
  return [hook.memoizedState, dispatch];

memoizedState

react其实不知道我们调用了几次useState。
所以还是在memoizedState上动手脚,这个处理体现在mountWorkInProgressHook

 memoizedState: 
  baseState,
  next,
  baseUpdate,
  queue,
  memoizedState

memoizedState.next就是下一次useState的hook对象。

hook1 === Fiber.memoizedState
state1 === hook1.memoizedState
state2 = hook1.next.memoizedState

因为以这种方式存储,所以usestate必须在functionalComponent的根作用域中。不能被for,和if。

setstate

mountState函数返回的是 return [hook.memoizedState, dispatch];
dispatch通过闭包就可以处理state。

更新

useState在更新的时候是调用的updateState,这个函数其实是封装的updateReducer。

function renderWithHooks()
    ReactCurrentDispatcher.current =
      nextCurrentHook === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
;
HooksDispatcherOnMount: 
    useState: mountState,

HooksDispatcherOnUpdate: 
    useState: updateState,

updateReducer

可以看到updateReducer把新的fiber中的state值更新,返回新的值。然后后续走渲染流程。(之前写过reat 的渲染流程)
还可以看到这有个循环update = update.next; while (update !== null && update !== first);
这就是hooks的batchUpdate。

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] 
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;

  queue.lastRenderedReducer = reducer;

  // ...
  // The last update in the entire queue
  const last = queue.last;
  // The last update that is part of the base state.
  const baseUpdate = hook.baseUpdate;
  const baseState = hook.baseState;

  // Find the first unprocessed update.
  let first;
  if (baseUpdate !== null) 
    if (last !== null) 
      // For the first update, the queue is a circular linked list where
      // `queue.last.next = queue.first`. Once the first update commits, and
      // the `baseUpdate` is no longer empty, we can unravel the list.
      last.next = null;
    
    first = baseUpdate.next;
   else 
    first = last !== null ? last.next : null;
  
  if (first !== null) 
    let newState = baseState;
    let newBaseState = null;
    let newBaseUpdate = null;
    let prevUpdate = baseUpdate;
    let update = first;
    let didSkip = false;
    do 
      const updateExpirationTime = update.expirationTime;
      if (updateExpirationTime < renderExpirationTime) 
        // Priority is insufficient. Skip this update. If this is the first
        // skipped update, the previous update/state is the new base
        // update/state.
        if (!didSkip) 
          didSkip = true;
          newBaseUpdate = prevUpdate;
          newBaseState = newState;
        
        // Update the remaining priority in the queue.
        if (updateExpirationTime > remainingExpirationTime) 
          remainingExpirationTime = updateExpirationTime;
        
       else 
        markRenderEventTimeAndConfig(
          updateExpirationTime,
          update.suspenseConfig,
        );

        // Process this update.
        if (update.eagerReducer === reducer) 
          // If this update was processed eagerly, and its reducer matches the
          // current reducer, we can use the eagerly computed state.
          newState = ((update.eagerState: any): S);
         else 
          const action = update.action;
          newState = reducer(newState, action);
        
      
      prevUpdate = update;
      update = update.next;
     while (update !== null && update !== first);

    if (!didSkip) 
      newBaseUpdate = prevUpdate;
      newBaseState = newState;
    

    // Mark that the fiber performed work, but only if the new state is
    // different from the current state.
    if (!is(newState, hook.memoizedState)) 
      markWorkInProgressReceivedUpdate();
    

    hook.memoizedState = newState;
    hook.baseUpdate = newBaseUpdate;
    hook.baseState = newBaseState;

    queue.lastRenderedState = newState;
  

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];

以上是关于react 16 Hooks渲染流程的主要内容,如果未能解决你的问题,请参考以下文章

更改数组中的一个状态会导致在 React Hooks 中重新渲染整个循环生成的自定义组件

React Hooks 渲染的钩子比上一次渲染时更多

React Hooks究竟是什么呢?

2-2-2 React16+ 理解 React Hooks

React Hooks:切换模式下的重新渲染次数过多

使用 React Hooks 重新渲染的次数过多