ReactHooks源碼解析之useState及爲何useState要按順序執行

前言

從本篇開始,咱們講 React-Hooks 最經常使用的幾個函數,先經過例子來看下React.useState()javascript

import React, {useEffect} from 'react';
import React from 'react';

function App({
  debugger
  const [name, setName] = React.useState( 'chen');

  return (
      <div onClick={()=>setName('jin')}>
        {name}
      </div>

  );
}

export default App;
複製代碼

當執行App()時,會調用React.useState('chen'),由於是useState()的第一次調用,因此此時就會執行源碼裏的mountState()php

1、mountState()

做用:

初始化useState(),並返回一個數組html

源碼:
//第一次更新 state 走這裏
//useState的核心源碼
//initialState 就是 React.useState(initialState) 設的初始值
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  //若是 initValue 是 function 的話,則獲取執行的結果
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  //注意下這邊的語法,連等賦值,等同於:
  //hook.queue = { xxx }
  //const queue = hook.queue
  const queue = (hook.queue = {
    last: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });
  //這邊又是一個連等賦值
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    //注意,由於 FunctionComponent 沒有 this,因此 bind()第一個參數是 null
    null,
    // Flow doesn't know this is non-null, but we do.
    //當前正要渲染的 fiber 對象
    ((currentlyRenderingFiber: any): Fiber),
    queue,
  ): any));
  //initialState,dispatchAction.bind(null,currentlyRenderingFiber,queue,)
  //開發層面:const [name,setName]=React.useState('chen')
  //那麼 name就是hook.memoizedState,賦值了'chen'
  //setName 就是 dispatch,即 dispatchAction.bind(null,currentlyRenderingFiber,queue,)
  return [hook.memoizedState, dispatch];
}
複製代碼
解析:

(1) 執行mountWorkInProgressHook(),獲取最新的hook 鏈表java

mountWorkInProgressHook()源碼以下:react

//將當前 hook 加入 workInProgressHook 鏈表中,
//並返回最新的 hook 鏈表
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedStatenull,
    baseStatenull,
    queuenull,
    baseUpdatenull,
    nextnull,
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    firstWorkInProgressHook = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }
  //獲取最新的 hook 鏈表,並返回
  return workInProgressHook;
}
複製代碼

(2) 若是initialState是一個callback的話,好比:git

const [styleObj, setStyleObj] = React.useState(()=>{return {height:14,} });
複製代碼

那麼就會去執行initialStategithub

 //若是 initValue 是 function 的話,則獲取執行的結果
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
複製代碼

(3) 而後初始化了hook.memoizedStatehook.baseStatehook.queuequeue.dispatchweb

(4) basicStateReducer的源碼以下:sql

//傳入參數 A、B,若是 B 是 function,則返回 B(A),不然返回 B
function basicStateReducer<S>(state: S, action: BasicStateAction<S>)S {
  return typeof action === 'function' ? action(state) : action;
}
複製代碼

(5) 注意下dispatchAction()的使用:json

dispatchAction.bind(
    //注意,由於 FunctionComponent 沒有 this,因此 bind()第一個參數是 null
    null,
    // Flow doesn't know this is non-null, but we do.
    //當前正要渲染的 fiber 對象
    ((currentlyRenderingFiber: any): Fiber),
    queue,
  )
複製代碼

由於 React-Hooks 只適用於FunctionComponent,因此綁定了thisnull,而且傳入了參數——currentlyRenderingFiberqueue

(6) 最終返回一個數組:

[hook.memoizedState, dispatch]
複製代碼

在開發層面
chen賦值給了hook.memoizedState

setName表示dispatch注意,這裏 setName 只是形參,並無賦值給 dispatch

(7) 此時的name 爲'chen',當點擊 div,調用setName('jin')時,會執行dispatchAction('jin')函數,咱們來看下dispatchAction源碼

2、dispatchAction()

做用:

(1) 新建 update 對象:{action:'jin'}
(2) 將 update 加至 hook.queue 的末尾:hook.queue.last = update
(3) 執行 scheduleWork(),走 updateFunctionComponent() 流程

源碼:
//一、新建 update 對象:{action:'jin'}
//二、將 update 加至 hook.queue 的末尾:hook.queue.last = update
//三、執行 scheduleWork(),走 updateFunctionComponent() 流程
function dispatchAction<SA>(
  fiber: Fiber, //當前正要渲染的 fiber 對象
  queue: UpdateQueue<S, A>,
  action: A, //'jin'
{
  invariant(
    numberOfReRenders < RE_RENDER_LIMIT,
    'Too many re-renders. React limits the number of renders to prevent ' +
      'an infinite loop.',
  );

  //刪除了 dev 代碼

  const alternate = fiber.alternate;
  //因爲這邊的currentlyRenderingFiber爲 null,傳進來的fiber(currentlyRenderingFiber)有值,因此走 else 狀況
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // This is a render phase update. Stash it in a lazily-created map of
    // queue -> linked list of updates. After this render pass, we'll restart
    // and apply the stashed updates on top of the work-in-progress hook.
    didScheduleRenderPhaseUpdate = true;
    const update: Update<S, A> = {
      expirationTime: renderExpirationTime,
      suspenseConfignull,
      action,
      eagerReducernull,
      eagerStatenull,
      nextnull,
    };
    if (renderPhaseUpdates === null) {
      renderPhaseUpdates = new Map();
    }
    const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
    //獲取update,沒有則初始化
    if (firstRenderPhaseUpdate === undefined) {
      renderPhaseUpdates.set(queue, update);
    }
    //若是有 update 的話,則遍歷至 update 隊尾,將最新的 update 添加至隊尾
    else {
      // Append the update to the end of the list.
      let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
      while (lastRenderPhaseUpdate.next !== null) {
        lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
      }
      lastRenderPhaseUpdate.next = update;
    }
  }

  else {
    if (revertPassiveEffectsChange) {
      flushPassiveEffects();
    }

    const currentTime = requestCurrentTime();
    const suspenseConfig = requestCurrentSuspenseConfig();
    const expirationTime = computeExpirationForFiber(
      currentTime,
      fiber,
      suspenseConfig,
    );
    //新建 update 對象
    //注意 action 就是傳進來要更新的 state->'jin'
    const update: Update<S, A> = {
      expirationTime,
      suspenseConfig,
      action,
      eagerReducernull,
      eagerStatenull,
      nextnull,
    };

    // Append the update to the end of the list.
    //將 update 加至 hook.queue 的末尾
    const last = queue.last;
    if (last === null) {
      // This is the first update. Create a circular list.
      update.next = update;
    } else {
      const first = last.next;
      if (first !== null) {
        // Still circular.
        update.next = first;
      }
      last.next = update;
    }
    queue.last = update;
    //fiber的優先級expirationTime爲 0,和NoWork值相等,
    //而且alternate(也就是fiber的副本)的expirationTime也爲 0,因此條件成立
    if (
      fiber.expirationTime === NoWork &&
      (alternate === null || alternate.expirationTime === NoWork)
    ) {
      // The queue is currently empty, which means we can eagerly compute the
      // next state before entering the render phase. If the new state is the
      // same as the current state, we may be able to bail out entirely.
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;

        //刪除了 dev 代碼

        try {
          const currentState: S = (queue.lastRenderedState: any);
          const eagerState = lastRenderedReducer(currentState, action);
          // Stash the eagerly computed state, and the reducer used to compute
          // it, on the update object. If the reducer hasn't changed by the
          // time we enter the render phase, then the eager state can be used
          // without calling the reducer again.

          //queue.last 也會同步更新,由於是同一引用地址
          update.eagerReducer = lastRenderedReducer;
          // update.eagerState = 'jin'
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            // Fast path. We can bail out without scheduling React to re-render.
            // It's still possible that we'll need to rebase this update later,
            // if the component re-renders for a different reason and by that
            // time the reducer has changed.
            return;
          }
        } catch (error) {
          // Suppress the error. It will throw again in the render phase.
        } finally {
          if (__DEV__) {
            ReactCurrentDispatcher.current = prevDispatcher;
          }
        }
      }
    }

    //刪除了 dev 代碼
    //最後執行scheduleWork(),以後會到 updateFunctionComponent 那邊
    //關於 scheduleWork 的講解,請看:
    //[React源碼解析之scheduleWork(上)](https://juejin.im/post/5d7fa983f265da03cf7ac048)
    //[React源碼解析之scheduleWork(下)](https://juejin.im/post/5d885b75f265da03e83baaa7)
    scheduleWork(fiber, expirationTime);
  }
}
複製代碼
解析:

(1) 首先注意下傳進來的fiber和這裏面的currentlyRenderingFiber不是同一個fiber
此時currentlyRenderingFiber爲 null,由於尚未進行FunctionComponent的更新
但傳進來的fiber是有值的,也就是App()

(2) 因此,如下條件不成立:

  //因爲這邊的currentlyRenderingFiber爲 null,傳進來的fiber(currentlyRenderingFiber)有值,因此走 else 狀況
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {

  }
複製代碼

看下 else 狀況

(3) 新建 update 對象:

    const updateUpdate<S, A> = {
      expirationTime,
      suspenseConfig,
      action,
      eagerReducer: null,
      eagerState: null,
      nextnull,
    };
複製代碼

注意:
action就是傳進來要更新的state—>'jin'

(4) 將update對象加至hook.queue的末尾

    //將 update 加至 hook.queue 的末尾
    const last = queue.last;
    if (last === null) {
      // This is the first update. Create a circular list.
      update.next = update;
    } else {
      const first = last.next;
      if (first !== null) {
        // Still circular.
        update.next = first;
      }
      last.next = update;
    }
    queue.last = update;
複製代碼

(5) fiber的優先級expirationTime爲 0,和NoWork值相等,而且alternate(也就是fiber的副本)的expirationTime也爲 0,因此條件成立

  if (
      fiber.expirationTime === NoWork &&
      (alternate === null || alternate.expirationTime === NoWork)
    ) {

  }
複製代碼

(6) lastRenderedReducer1、mountState()中「解析」的(4)提到過,是有值的,因此走這邊:

if (lastRenderedReducer !== null) {

}
複製代碼

(7) 如下代碼的目的是將'jin'賦值到update.eagerState中,方便以後用到:

  const currentState: S = (queue.lastRenderedState: any);
          const eagerState = lastRenderedReducer(currentState, action);
          //queue.last 也會同步更新,由於是同一引用地址
          update.eagerReducer = lastRenderedReducer;
          // update.eagerState = 'jin'
          update.eagerState = eagerState;
複製代碼

(8) 最後執行scheduleWork()方法,爲何以後會到updateFunctionComponent那邊,能夠自行斷點調試查看:

(9) 關於scheduleWork()的講解,請看:
React源碼解析之scheduleWork(上)
React源碼解析之scheduleWork(下)

3、從updateFunctionComponent()到updateState()

(1) updateFunctionComponent()源碼中,有一個renderWithHooks()方法:

  //在渲染的過程當中,對裏面用到的 hooks 函數作一些操做
    nextChildren = renderWithHooks(
      current,
      workInProgress,
      Component,
      nextProps,
      context,
      renderExpirationTime,
    );
複製代碼

(2) renderWithHooks()中有一個Component()方法:

  //workInProgress.type,這裏能當作 function 使用,說明 type 是 function
  //App()
  let children = Component(props, refOrContext);
複製代碼

注意:
這裏的Component,是workInProgress.type,也就是App方法:

function App({
  debugger
  const [name, setName] = React.useState( 'chen');

  return (
      <div onClick={()=>setName('jin')}>
        {name}
      </div>

  );
}
複製代碼

執行App()後,又會到const [name, setName] = React.useState( 'chen');上,但此時的useState調用的不是源碼中的mountState(),而是updateState('chen')!!

4、updateState()

做用:

屢次更新initialState的值

源碼:
//屢次更新 state 走這裏
function updateState<S>(
  initialState: (() => S) | S, //'chen'
): [S, Dispatch<BasicStateAction<S>>] {
  //basicStateReducer 源碼:action === 'function' ? action(state) : action;
  return updateReducer(basicStateReducer, (initialState: any));
}
複製代碼
解析:

(1) 能夠看到,此時的updateState,實際上是調用的updateReducer
也就是說:
此時,useState()的本質是useReducer()

關於useReducer()的做用及使用,請看:
zh-hans.reactjs.org/docs/hooks-…

(2) 注意此時的initialState仍然是'chen'

5、updateReducer()

做用:

屢次更新initialState的值

源碼:
//useState 屢次更新時調用 updateReducer
//reducer:basicStateReducer
//initialArg:initialState
function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  //當前正在 update 的 hook
  //{
  // memoizedState: "chen"
  // baseState: "chen"
  // queue:{
  //  last:{
  //    expirationTime: 1073741823
  //    action: "jin",
  //    eagerState: "jin"
  //    next:是 last 對象,因此 queue 是單向鏈表
  //  }
  // }
  //}
  const hook = updateWorkInProgressHook();
  //hook 的更新隊列
  const queue = hook.queue;
  invariant(
    queue !== null,
    'Should have a queue. This is likely a bug in React. Please file an issue.',
  );
  //()=>{ return typeof action === 'function' ? action(state) : action;}
  queue.lastRenderedReducer = reducer;
  //numberOfReRenders=0,下面不看
  if (numberOfReRenders > 0) {
    // This is a re-render. Apply the new render phase updates to the previous
    // work-in-progress hook.
    const dispatch: Dispatch<A> = (queue.dispatch: any);
    if (renderPhaseUpdates !== null) {
      // Render phase updates are stored in a map of queue -> linked list
      const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
      if (firstRenderPhaseUpdate !== undefined) {
        renderPhaseUpdates.delete(queue);
        let newState = hook.memoizedState;
        let update = firstRenderPhaseUpdate;
        do {
          // Process this render phase update. We don't have to check the
          // priority because it will always be the same as the current
          // render's.
          const action = update.action;
          newState = reducer(newState, action);
          update = update.next;
        } while (update !== null);

        // 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;
        // Don't persist the state accumlated from the render phase updates to
        // the base state unless the queue is empty.
        // TODO: Not sure if this is the desired semantics, but it's what we
        // do for gDSFP. I can't remember why.
        if (hook.baseUpdate === queue.last) {
          hook.baseState = newState;
        }

        queue.lastRenderedState = newState;

        return [newState, dispatch];
      }
    }
    return [hook.memoizedState, dispatch];
  }

  // The last update in the entire queue
  //獲取hook 更新隊列上最新的 update 對象
  const last = queue.last;
  // The last update that is part of the base state.
  const baseUpdate = hook.baseUpdate; //baseUpdate = null
  const baseState = hook.baseState; //baseState = "chen"

  // Find the first unprocessed update.
  let first;
  //baseUpdate = null
  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;
  }
  //first:{
  //   expirationTime: 1073741823
  //   action: "jin",
  //   eagerState: "jin"
  //   next:first 對象
  //  }
  if (first !== null) {
    let newState = baseState; //baseState = "chen"
    let newBaseState = null;
    let newBaseUpdate = null;
    let prevUpdate = baseUpdate; //baseUpdate = null
    let update = first; //object
    let didSkip = false;
    do {
      const updateExpirationTime = update.expirationTime; //1073741823
      //二者相等,跳過
      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 {
        // This update does have sufficient priority.

        // Mark the event time of this update as relevant to this render pass.
        // TODO: This should ideally use the true event time of this update rather than
        // its priority which is a derived and not reverseable value.
        // TODO: We should skip this update if it was already committed but currently
        // we have no way of detecting the difference between a committed and suspended
        // update here.
        markRenderEventTimeAndConfig(
          updateExpirationTime,
          update.suspenseConfig,
        );

        // Process this update.
        // update.eagerReducer : basicStateReducer function
        // reducer : basicStateReducer function
        //二者相同,走這裏
        //獲取 update 的 state 值,新值
        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); //eagerState: "jin"

        } else {
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      prevUpdate = update;
      update = update.next;
      //update.next 就是 last 對象,last 對象就是 first,二者相等,跳出循環
    } 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.
    //判斷 memoizedState(oldState) 和 newState 是否真的不一樣
    if (!is(newState, hook.memoizedState)) {
      //標記當前 fiber 的確收到了 update
      markWorkInProgressReceivedUpdate();
    }

    hook.memoizedState = newState; //newState = "jin"
    hook.baseUpdate = newBaseUpdate;
    hook.baseState = newBaseState; //newBaseState = "jin"

    queue.lastRenderedState = newState;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  //[name,setName]
  //dispatchAction
  return [hook.memoizedState, dispatch];
}
複製代碼
解析:

(1) 執行updateWorkInProgressHook(),獲取當前正在updatehook

  const hook = updateWorkInProgressHook();
複製代碼

簡單看下updateWorkInProgressHook()的源碼:

//當前正在 update 的 fiber 上的 hook
function updateWorkInProgressHook(): Hook {
  // This function is used both for updates and for re-renders triggered by a
  // render phase update. It assumes there is either a current hook we can
  // clone, or a work-in-progress hook from a previous render pass that we can
  // use as a base. When we reach the end of the base list, we must switch to
  // the dispatcher used for mounts.
  if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
    nextCurrentHook = currentHook !== null ? currentHook.next : null;
  } else {
    // Clone from the current hook.
    invariant(
      nextCurrentHook !== null,
      'Rendered more hooks than during the previous render.',
    );
    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      queue: currentHook.queue,
      baseUpdate: currentHook.baseUpdate,

      nextnull,
    };

    if (workInProgressHook === null) {
      // This is the first hook in the list.
      workInProgressHook = firstWorkInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      workInProgressHook = workInProgressHook.next = newHook;
    }
    nextCurrentHook = currentHook.next;
  }
  return workInProgressHook;
}
複製代碼

最後返回的workInProgressHook,大概是這樣:

  const workInProgressHook={
  memoizedState: "chen",
  baseState: "chen",
  queue:{
   last:{
     expirationTime: 1073741823,
     action: "jin",
     eagerState: "jin",
     next:  //是 last 對象,因此 queue 是單向鏈表
   }
  }
  }
複製代碼

(2) hook.queuehook的更新隊列,咱們須要更新的值就在queue.last. eagerState/action中:

  const queue = hook.queue;
複製代碼

(3) numberOfReRenders表示從新渲染時fiber的節點數,也就是在render 的時候,又產生了 update 時,會加 1,但這裏的numberOfReRenders爲 0,因此不走這邊:

if (numberOfReRenders > 0) {

}
複製代碼

(4) 賦值firsthook.queue.last.next

  // Find the first unprocessed update.
  let first;
  //baseUpdate = null
  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;
  }
複製代碼

first大概長這樣:

  const first={
    expirationTime: 1073741823,
    action: "jin",
    eagerState: "jin",
    next:  //仍是first 對象
   }
複製代碼

(5) 由於first不爲 null,因此走這邊

if (first !== null) {
    let newState = baseState; //baseState = "chen"
    let newBaseState = null;
    let newBaseUpdate = null;
    let prevUpdate = baseUpdate; //baseUpdate = null
    let update = first; //object
    let didSkip = false;
    do {
      const updateExpirationTime = update.expirationTime; //1073741823
      //二者相等,跳過
      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 {
        // This update does have sufficient priority.

        // Mark the event time of this update as relevant to this render pass.
        // TODO: This should ideally use the true event time of this update rather than
        // its priority which is a derived and not reverseable value.
        // TODO: We should skip this update if it was already committed but currently
        // we have no way of detecting the difference between a committed and suspended
        // update here.
        markRenderEventTimeAndConfig(
          updateExpirationTime,
          update.suspenseConfig,
        );

        // Process this update.
        // update.eagerReducer : basicStateReducer function
        // reducer : basicStateReducer function
        //二者相同,走這裏
        //獲取 update 的 state 值,新值
        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); //eagerState: "jin"

        } else {
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      prevUpdate = update;
      update = update.next;
      //update.next 就是 last 對象,last 對象就是 first,二者相等,跳出循環
    } 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.
    //判斷 memoizedState(oldState) 和 newState 是否真的不一樣
    if (!is(newState, hook.memoizedState)) {
      //標記當前 fiber 的確收到了 update
      markWorkInProgressReceivedUpdate();
    }

    hook.memoizedState = newState; //newState = "jin"
    hook.baseUpdate = newBaseUpdate;
    hook.baseState = newBaseState; //newBaseState = "jin"

    queue.lastRenderedState = newState;
  }
複製代碼

注意這邊是一個do..while循環,跳出條件是:

!(update !== null && update !== first)
複製代碼

while循環中,由於updateExpirationTime是和renderExpirationTime相等的,由於:

export function renderWithHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any, //render
  props: any,
  refOrContext: any, //ref
  nextRenderExpirationTime: ExpirationTime,
):
{
  //本次 render 時候的優先級
  renderExpirationTime = nextRenderExpirationTime;
  //...
}
複製代碼

因此不走這邊:

  //二者相等,跳過
  if (updateExpirationTime < renderExpirationTime) {

  }
複製代碼

(6) 而後走這裏:

//走這裏
      else {
        //刪除了一些代碼

        // Process this update.
        // update.eagerReducer : basicStateReducer function
        // reducer : basicStateReducer function
        //二者相同,走這裏
        //獲取 update 的 state 值,新值
        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); //eagerState: "jin"

        } else {
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
複製代碼


由於update.eagerState等於2、dispatchAction()中的queue.lastRenderedReducer,等於`1、mountState()中的basicStateReducer,因此update.eagerReducer === reducer條件成立,走這邊:

newState = ((update.eagerState: any): S);
複製代碼

此時newState=update.eagerState='jin',而後由於update.next就是last對象,last對象就是first,二者相等,跳出while循環

(7) 跳出while循環後走這邊:

    hook.memoizedState = newState; //newState = "jin"
    hook.baseUpdate = newBaseUpdate;
    hook.baseState = newBaseState; //newBaseState = "jin"

  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  //[name,setName]
  //dispatchAction
  return [hook.memoizedState, dispatch];
複製代碼

'jin'賦值給hook.memoizedState,返回['jin', dispatch]

(8) 到開發層面,此時name已更新爲'jin'

function App({
  debugger
  const [name, setName] = React.useState( 'chen');

  return (
      <div onClick={()=>setName('jin')}>
        {name}
      </div>

  );
}
複製代碼

useState源碼解析部分就結束了,接下來看下 下面這個問題

6、爲何useState要按順序執行

若是你時常查閱 React 官方文檔的話,確定會看到這個規則:

zh-hans.reactjs.org/docs/hooks-…

爲何useState要按順序執行呢?

咱們注意下mountWorkInProgressHook()firstWorkInProgressHook

由圖能夠看到,當初始化三個 useState 時,Hooks鏈是經過next來綁定三個state的順序的,若是在屢次調用 Hooks 時,將某一個useState有條件的省略掉,不執行,那麼.next的時候,獲取的 state 就不是理想的 state,會形成state錯位,因此 React 官方已經禁止這樣作了:

7、useState流程圖

useState流程圖.jpg
useState流程圖.jpg

8、GitHub

mountState/dispatchAction/renderWithHooks/updateState/updateReducer
github.com/AttackXiaoJ…

updateFunctionComponent
github.com/AttackXiaoJ…


(完)

相關文章
相關標籤/搜索