xinpianchang / fe-weekly

weekly for fe-team
10 stars 2 forks source link

react 之useState #74

Open dailynodejs opened 3 years ago

dailynodejs commented 3 years ago

流程图

两个分支:

image

源码地址:https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.development.js

beginWork

image

function beginWork(current, workInProgress, renderLanes) {
  // ...
  switch (workInProgress.tag) {
    case IndeterminateComponent: 
    {
      return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
    }
  }
}

IndeterminateComponent

这里对应的是 FunctionComponent,所以才会和 hooks 关联

updateFunctionComponent

renderWithHooks

这里会判断走 HooksDispatcherOnMountInDEV 还是 HooksDispatcherOnUpdateInDEV

image

prod 包

https://github.com/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiberHooks.js#L355

// Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.  
// Non-stateful hooks (e.g. context) don't get added to memoizedState,  
// so nextCurrentHook would be null during updates and mounts.

if (__DEV__) {
  if (nextCurrentHook !== null) {
     ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV   
  } else if (hookTypeDev !== null) {
     ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV
  } else {
     ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV 
  }
} else {
  ReactCurrentDispatcher.current =  nextCurrentHook === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate
}

useState

function useState(initialState) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useState(initialState);
  }

resolveDispatcher

这里就用到了 ReactCurrentDispatcher.current

function resolveDispatcher() {
    var dispatcher = ReactCurrentDispatcher.current;

    if (!(dispatcher !== null)) {
      {
        throw Error( "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem." );
      }
    }

    return dispatcher;
  }

源码地址:https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.development.js

dev 环境

HooksDispatcherOnUpdateInDEV.useState

HooksDispatcherOnUpdateInDEV = {
  useState: function (initialState) {
    // ...
  }
}

image

prod 环境

https://github.com/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiberHooks.js#L1203

HooksDispatcherOnUpdate.useState

const HooksDispatcherOnUpdate: Dispatcher = {
  useState: updateState
}

updateState

更新走的,依赖了 updateReducer

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

updateReducer

function updateReducer(reducer, initialArg, init) {
    var hook = updateWorkInProgressHook();
    var queue = hook.queue;

    // ...
    var dispatch = queue.dispatch;
    return [hook.memoizedState, dispatch];
}

updateWorkInProgressHook

image

function updateWorkInProgressHook() {
    // 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.
    var nextCurrentHook;

    if (currentHook === null) {
      var current = currentlyRenderingFiber$1.alternate;

      if (current !== null) {
        nextCurrentHook = current.memoizedState;
      } else {
        nextCurrentHook = null;
      }
    } else {
      nextCurrentHook = currentHook.next;
    }

    var nextWorkInProgressHook;

    if (workInProgressHook === null) {
      nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState;
    } else {
      nextWorkInProgressHook = workInProgressHook.next;
    }

    if (nextWorkInProgressHook !== null) {
      // There's already a work-in-progress. Reuse it.
      workInProgressHook = nextWorkInProgressHook;
      nextWorkInProgressHook = workInProgressHook.next;
      currentHook = nextCurrentHook;
    } else {
      // Clone from the current hook.
      if (!(nextCurrentHook !== null)) {
        {
          throw Error( "Rendered more hooks than during the previous render." );
        }
      }

      currentHook = nextCurrentHook;
      var newHook = {
        memoizedState: currentHook.memoizedState,
        baseState: currentHook.baseState,
        baseQueue: currentHook.baseQueue,
        queue: currentHook.queue,
        next: null
      };

      if (workInProgressHook === null) {
        // This is the first hook in the list.
        currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
      } else {
        // Append to the end of the list.
        workInProgressHook = workInProgressHook.next = newHook;
      }
    }

    return workInProgressHook;
  }
dailynodejs commented 3 years ago

第一次

调用栈的图示

image

开发包

HooksDispatcherOnMountInDEV = {
  useState: function (initialState) {
    // ...
  }
}

image

prod 包

https://github.com/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiberHooks.js#L1188

const HooksDispatcherOnMount: Dispatcher = {
  useState: mountState,
}

mountState

image

调用了 mountWorkInProgressHook

mountWorkInProgressHook

function mountWorkInProgressHook() {
    var hook = {
      memoizedState: null,
      baseState: null,
      baseQueue: null,
      queue: null,
      next: null
    };

    if (workInProgressHook === null) {
      // This is the first hook in the list
      currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
    } else {
      // Append to the end of the list
      workInProgressHook = workInProgressHook.next = hook;
    }

    return workInProgressHook;
  }