react hooks实现源码解析

241 阅读5分钟
  • 本文解析react实现hooks相关功能的源码,分析出主要思路和函数。
  • 在hooks出来之前,函数组件内部不能持有状态,也不能触发副作用,但是hooks增强了函数组件,本文看看useState是如何实现的。
  function useState<S>(
    initialState: S
  ): [S, Dispatch<S>] {
    const dispather = resolveDispatcher();
    // 实际上运行的是dispatcher上的useState方法
    return dispatcher.useState(initialState);
  }
  function resolveDispatcher() {
    // 返回的是全局对象的一个属性,全局对象在运行函数组件的时候动态更新,具体见下面
    return ReactCurrentDispatcher.current;
  }
  // 函数组件就是在renderWithHooks中调用的
  // 在执行函数组件的前后,会修改全局变量,让hook函数生效或失效
  function renderWithHooks(current: Fiber, Component: any) {
    // 开始设置全局变量,注意到这里根据current是否为空设置了不同的全局对象
    ReactCurrentDispatcher.current = 
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
    
    // Component就是函数组件本身
    let children = Component(props);
    
    // 结束设置全局变量
    ReactCurrentDispather.current = ContextOnlyDispatcher;
  }
  // 结束设置全局变量:ContextOnlyDispatcher
  export const ContextOnlyDispatcher: Dispatcher = {
    useCallback: throwInvalidHookError,
    useContext: throwInvalidHookError,
    useEffect: throwInvalidHookError,
    useImperativeHandle: throwInvalidHookError,
    useInsertionEffect: throwInvalidHookError,
    useLayoutEffect: throwInvalidHookError,
    useMemo: throwInvalidHookError,
    useReducer: throwInvalidHookError,
    useRef: throwInvalidHookError,
    useState: throwInvalidHookError,
    useDebugValue: throwInvalidHookError,
    useDeferredValue: throwInvalidHookError,
    useTransition: throwInvalidHookError,
    useMutableSource: throwInvalidHookError,
    useSyncExternalStore: throwInvalidHookError,
    useId: throwInvalidHookError,
  };
  // 所有的hook函数都会抛出异常,提醒我们不能在函数组件外部使用hooks
  function throwInvalidHookError() {
    throw new 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:\n' +
        '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
        '2. You might be breaking the Rules of Hooks\n' +
        '3. You might have more than one copy of React in the same app\n' +
        'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
    );
  }
  // current为空时的赋值
  const HooksDispatcherOnMount: Dispatcher = {
    useCallback: mountCallback,
    useContext: readContext,
    useEffect: mountEffect,
    useImperativeHandle: mountImperativeHandle,
    useLayoutEffect: mountLayoutEffect,
    useInsertionEffect: mountInsertionEffect,
    useMemo: mountMemo,
    useReducer: mountReducer,
    useRef: mountRef,
    useState: mountState,
  };
  // current不为空时的赋值
  const HooksDispatcherOnUpdate: Dispatcher = {
    useCallback: updateCallback,
    useContext: readContext,
    useEffect: updateEffect,
    useImperativeHandle: updateImperativeHandle,
    useInsertionEffect: updateInsertionEffect,
    useLayoutEffect: updateLayoutEffect,
    useMemo: updateMemo,
    useReducer: updateReducer,
    useRef: updateRef,
    useState: updateState,
  };
  // 根据current是否为空,实际上运行hooks是不同的方法
  // 例如useState:第一次运行的是mountState,第二次及以后运行的都是updateState

小结

  • react在运行时动态修改全局变量,保证hook函数无法在函数组件外部执行
  • 同一个hook函数在第一次和非第一次是调用不同的函数。如useState分别调用mountStateupdateState

mountState

  // mounteState创建一个hook对象,并关联上当前函数组件对应的fiber的hook
  function mountState(initState) {
    // 创建一个hook对象
    const hook = mountWorkInProgressHook();
    // 将初始值保存到hook对象上
    hook.memoizedState = hook.baseState = initState;
    const queue = {
      pending: null,
      dispatch: null,
    }
    hook.queue = queue;
    // 更新函数,已经关联当前fiber和更新队列
    const dispatch = queue.dispatch = dispatchSetState.bind(
      null,
      currentlyRenderingFiber,
      queue
    )
    // 数组的第一个元素就是当前值,第二个元素用于触发更新
    return [hook.memoizedState, dispatch];
  }
  
  // dispatchSetState的实现和类似类组件中的setState
  function dispatchSetState(fiber: Fiber, queue: UpdateQueue, action) {
    const update = { action };
    enqueueUpdate(fiber, queue, update);
    schedueUpdateOnFiber(fiber)
  }
  // 作用就是每调用一次hook函数,则新建一个hook对象
  function mountWorkInProgressHook() {
    // 新建一个hook对象
    const hook = {
      memoizedState: null,
      baseState: null,
      baseQueue: null,
      queue: null,
      next: null,
    }
    if (workInProgressHook === null) {
      // workInProgressHook是一个全局变量,表示当前workInProgress已有的Hook对象
      // workInProgressHook为空,说明当前函数组件第一次调用hook函数
      // fiber对象的memoizedState也指向第一个hook对象
      currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
    } else {
      // 说明当前函数组件不是第一次调用hook函数,则通过next关联起来
      workInProgressHook = workInProgressHook.next = hook;
    }
    return workInProgressHook;
  }

小结

  • mountState实际上就是创建hook对象
  • fiber.memoizedState指向第一个hook对象
  • 调用hook函数n次,则创建n个hook对象,通过next组成链表

updateState

  // 当再次运行函数组件,调用到useState时,实际上调用的是updateState函数  
  // updateState函数实际上调用updateReducer函数  
  function updateState() {  
    return updateReducer(basicStateReducer);  
  }  
  function updateReducer(reducer) {
    // 返回当前hook函数对应的hook对象
    const workInProgressHook = updateWorkInProgressHook();  
    const queue = workInProgressHook.queue; 
    const baseQueue = currentHook.baseQueue;  // 尚未处理的更新队列  
    const pendingQueue = queue.pending;   // 新的更新队列  
      
    // 将新的更新队列合并到尚未更新的队列中  
    if (pendingQueue !== null) {
      // 当前hook还有遗留的更新(老的更新)
      if (baseQueue !== null) {  
        // 把新的更新合并到老的更新中  
        const baseFirst = baseQueue.next;  
        const pendingFirst = pendingQueue.next;  
        baseQueue.next = pendingFirst;  
        pendingQueue.next = baseFirst;
      }
      // current.baseQueue上又是全部的更新,queue.pending就可以清空  
      currentHook.baseQueue = baseQueue = pendingQueue;  
      queue.pending = null  
    }  
      
    // 如果有更新需要处理,则计算得到新的state  
    if (baseQueue !== null) {  
      const first = baseQueue.next;  
      let update = first;  
      let newState = currentHook.baseState;  
      
      let newBaseQueueFirst = null; // 本次不处理的更次队列的第一个update对象  
      let newBaseQueueLast = null;  // 本次不处理的更新队列的最后一个update对象  
      do {
        const updateLane = update.lane;  
        if (!isSubsetOfLanes(renderLanes, updateLane)){  
          // 本次重新渲染不处理update对象,那么就copy一份  
          const clone = {  
            action: update.action,  
            next: null,  
          }
          if (newBaseQueueLast !== null) {
            newBaseQueueFirst = newBaseQueueLast = clone;
            newBaseState = newState;
          } else {
            // 关联到尾巴上
            newBaseQueueLast = newBaseQueueLast.next = clone;
          }
        } else {
          // 计算得到新的state
          const acition = update.action;
          newState = reducer(newState, action);
        }
    
        update = update.next;
      } while(update !== null && update !== first)  
      
      if (newBaseQueueLLast === null) {  
        // 说明上面代码全部走else分支,即newBaseState还是null  
        // 所以这里赋值为最新的state  
        newBaseState = newState;  
      } else {  
        // 说明newBaseQueueLast和newBaseQueueFirst都是有值的,使其成为一个环  
        newBaseQueueLast.next = newBaseQueueFirst;  
      }  
      
      // 保存新的state  
      workInProgressHook.memoizedState = newState;  
      // 保存新的baseState  
      workInProgressHook.baseState = newBaseState;  
      // 保存没有处理的更新队列  
      workInProgressHook.baseQueue = newBaseQueueLast;  
    }  
      
    const dispatch = queue.dispatch;  
    // 最终还是返回memoizedState  
    return [workInProgressHook.memoizedState, dispatch];  
  }
  // 作用找到正确的hook对象
  function updateWorkInProgressHook() {
    // 获取下一个currentHook对象
    // currentHook表示当前函数组件在current树上对应的fiber节点上的hook对象
    let nextCurrentHook;
    if (currentHook === null) {
      // 没有currentHook,表明第一次调用,所以尝试取memoizedState
      const current = currentlyRenderingFiber.alternate;
      if (current !== null) {
        nextCurrentHook = current.memoizedState
      } else {
        // 这里是什么情况呢?
        nextCurrentHook = null
      }
    } else {
      // 直接找下一个
      nextCurrentHook = currentHook.next;
    }
  
    // 获取下一个workInProgressHook对象
    // workInProgressHook表示workInProgress树的fiber对象的hook节点
    let nextWorkInProgressHook;
    if (workInProgressHook === null) {
      // workInProgressHook为空,说明查找的是第一个hook函数对应的hook对象
      // 则从fiber对象上找
      nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
    } else {
      // next就是下一个hook对象
      nextWorkInProgressHook = workInProgressHook.next;
    }
    
    currentHook = nextCurrentHook;
    if (nextWorkInProgressHook !== null) {
      // 找到了下一个hook对象,赋值到workInProgressHook
      workInProgressHook = nextWorkInProgressHook;
      nextWorkInProgressHook = nextWorkInProgressHook.next;
    } else {
      // 一般是当第二次fiber节点时。
      // 因为第一次渲染构建workInProgress,对应的alternate是null
      // 所以第二次构建workInProgress时,就可以复用第一次的hook对象
      if (nextCurrentHook === null) {
        // 说明workInProgress中的hook函数比上次多了,有问题
        throw new Error();
      }
      const newHook = {
        memoziedState: currentHook.memoziedState,
        baseState: currentHook.baseState,
        queue: currentHook.queue,
        next: null
      }
      if (workInProgressHook === null) {
        currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
      } else {
        workInProgressHook = workInProgressHook.next = newHook;
      }
    }
    return workInProgressHook;
  }

小结

  • updateWorkInProgressHook的作用就是找到正确的hook对象
  • updateReducer首先合并新老更新队列,如果需要更新,则计算得到新的memoizedState
  • TODO 在updateReducer中,newBaseState只是赋值成第一次不用更新时刻的newState的值,如果后续newState继续变化的话,那么newBaseState是不会更新的,这样不会有问题吗?