• 从react源码看hooks的原理


    React暴露出来的部分Hooks

    //packages/react/src/React.js
    export {
      ...
      useCallback,
      useContext,
      useEffect,
      useLayoutEffect,
      useMemo,
      useReducer,
      useRef,
      useState,
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    功能描述

    • useStateuseReducer: 状态值相关
    • useEffectuseLayoutEffect: 生命周期相关
    • useContext: 状态共享相关
    • useCallbackuseMemo: 性能优化相关
    • useRef: 属性相关

    源码

    export function useContext<T>(
      Context: ReactContext<T>,
      unstable_observedBits: number | boolean | void,
    ): T {
      const dispatcher = resolveDispatcher();
      ...
      return dispatcher.useContext(Context, unstable_observedBits);
    }
    
    export function useState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      const dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
    }
    
    export function useReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      const dispatcher = resolveDispatcher();
      return dispatcher.useReducer(reducer, initialArg, init);
    }
    
    export function useRef<T>(initialValue: T): {|current: T|} {
      const dispatcher = resolveDispatcher();
      return dispatcher.useRef(initialValue);
    }
    
    export function useEffect(
      create: () => (() => void) | void,  deps: Array<mixed> | void | null,
    ): void {
      const dispatcher = resolveDispatcher();
      return dispatcher.useEffect(create, deps);
    }
    
    export function useLayoutEffect(
      create: () => (() => void) | void,  deps: Array<mixed> | void | null,
    ): void {
      const dispatcher = resolveDispatcher();
      return dispatcher.useLayoutEffect(create, deps);
    }
    
    export function useCallback<T>(
      callback: T,
      deps: Array<mixed> | void | null,
    ): T {
      const dispatcher = resolveDispatcher();
      return dispatcher.useCallback(callback, deps);
    }
    
    export function useMemo<T>(
      create: () => T,
      deps: Array<mixed> | void | null,
    ): T {
      const dispatcher = resolveDispatcher();
      return dispatcher.useMemo(create, deps);
    }
    
    
    // resolveDispatcher
    function resolveDispatcher() {
      const dispatcher = ReactCurrentDispatcher.current;
      invariant(
       ...
      );
      return dispatcher;
    }
    
    // ReactCurrentDispatcher
    const ReactCurrentDispatcher = {
      /**   * @internal
       * @type {ReactComponent}   */
      current: (null: null | Dispatcher),
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    其实hooks的定义都来自dispatcher,那我们根据Dispatcher依次去看看他们的实际实现。

    Dispatcher

    export type Dispatcher = {|
      ...
      useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],
      useReducer<S, I, A>(
        reducer: (S, A) => S,
        initialArg: I,
        init?: (I) => S,
      ): [S, Dispatch<A>],
      useContext<T>(
        context: ReactContext<T>,
        observedBits: void | number | boolean,
      ): T,
      useRef<T>(initialValue: T): {|current: T|},
      useEffect(
        create: () => (() => void) | void,
        deps: Array<mixed> | void | null,
      ): void,
      useLayoutEffect(
        create: () => (() => void) | void,
        deps: Array<mixed> | void | null,
      ): void,
      useCallback<T>(callback: T, deps: Array<mixed> | void | null): T,
      useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T,
      ...
    |};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    很明显这就是各种hooks的定义,但是总归都要是参加到执行的的流程里面去的,函数组件也属于ReactComponent的一种,他也有mountupdate阶段。

    函数组件Mount阶段

    我们在前面提到执行beginWork函数中,我们发现会有tagFunctionComponent的选项,他会调用updateFunctionComponent进而调用renderWithHooks进行更新。

    // packages/react-reconciler/src/ReactFiberBeginWork.old.js
    function beginWork(
      current: Fiber | null,  workInProgress: Fiber,  renderLanes: Lanes,
    ): Fiber | null {
      const updateLanes = workInProgress.lanes;
      switch (workInProgress.tag) {
        case FunctionComponent: {
          const Component = workInProgress.type; // 组件类型
          const unresolvedProps = workInProgress.pendingProps; // 未处理的props
          ...
          return updateFunctionComponent(
            current,
            workInProgress,
            Component,
            resolvedProps,
            renderLanes,
          );
        }
        case ClassComponent: {
          const Component = workInProgress.type;
          const unresolvedProps = workInProgress.pendingProps;
          const resolvedProps =
            workInProgress.elementType === Component
              ? unresolvedProps
              : resolveDefaultProps(Component, unresolvedProps);
              // 渲染classComponent
          return updateClassComponent(
            current,
            workInProgress,
            Component,
            resolvedProps,
            renderLanes,
          );
        }
        case HostRoot:
          ...
        case HostComponent:
          ...
        case HostText:
          ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    相关参考视频讲解:进入学习

    renderWithHooks

    export function renderWithHooks<Props, SecondArg>(
      current: Fiber | null,
      workInProgress: Fiber,
      Component: (p: Props, arg: SecondArg) => any,
      props: Props,
      secondArg: SecondArg,
      nextRenderLanes: Lanes,
    ): any {
      // 下一个渲染任务的优先级
      renderLanes = nextRenderLanes;
      // fiber树
      currentlyRenderingFiber = workInProgress;
    
      ...
      // 重置memoizedState、updateQueue、lanes
      workInProgress.memoizedState = null;
      workInProgress.updateQueue = null;
      workInProgress.lanes = NoLanes;
      ...
    
      if (__DEV__) {
        ...
      } else {
        // 判断mount update阶段
        ReactCurrentDispatcher.current =
          current === null || current.memoizedState === null
            ? HooksDispatcherOnMount
            : HooksDispatcherOnUpdate;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 如果是mount阶段,则执行HooksDispatcherOnMount,之后再执行reconcileChildren
    • 如果是update阶段,则执行HooksDispatcherOnUpdate,之后再执行reconcileChildren

    HooksDispatcherOnMount

    const HooksDispatcherOnMount: Dispatcher = {
      useCallback: mountCallback,
      useContext: readContext,
      useEffect: mountEffect,
      useLayoutEffect: mountLayoutEffect,
      useMemo: mountMemo,
      useReducer: mountReducer,
      useRef: mountRef,
      useState: mountState,
      ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    mount的情况下,每个hook是都有自己对应的mountXxxuseState的对应的就是mountState,不过在讲mountState之前我们要去看一个东西 – type hook,因为实际的开发当中不可能只用一个hook,多个hook他就会形成链表,保存在fiber的memoizedState上面。

    type Hook

    export type Hook = {|
      memoizedState: any, // 单独的hook唯一值
      baseState: any, // 初始state
      baseQueue: Update<any, any> | null, // 初始更新队列
      queue: UpdateQueue<any, any> | null, // 更新队列
      next: Hook | null, // hooks链表下一个指针
    |};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    那么我们去看看他是如何关联起来的:const hook = mountWorkInProgressHook()

    function mountWorkInProgressHook(): Hook {
      const hook: Hook = {
        memoizedState: null,
        baseState: null,
        baseQueue: null,
        queue: null,
        next: null,
      };
    
      if (workInProgressHook === null) {
        // This is the first hook in the list
        // 第一个hook
        currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
      } else {
        // Append to the end of the list
        // 往后面添加,形成链表
        workInProgressHook = workInProgressHook.next = hook;
      }
      return workInProgressHook;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    mountState

    function mountState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
    
      // 创建并关联hook链表  
      const hook = mountWorkInProgressHook();
      // 如果是函数入参,则是把函数的执行结果当做入参
      if (typeof initialState === 'function') {
        // $FlowFixMe: Flow doesn't like mixed types
        initialState = initialState();
      }
      // 把hook的memoizedState变为初始值
      hook.memoizedState = hook.baseState = initialState;
    
      // 更新队列
      const queue = (hook.queue = {
        pending: null,
        dispatch: null,
        lastRenderedReducer: basicStateReducer,
        lastRenderedState: (initialState: any),
      });
      // 回调
      const dispatch: Dispatch<
        BasicStateAction<S>,
      > = (queue.dispatch = (dispatchAction.bind(
        null,
        currentlyRenderingFiber,
        queue,
      ): any));
      // 返回 memoizedState, dispatch
      return [hook.memoizedState, dispatch];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    根据mountState我们就知道在写useState的时候,解构的语法为什么会解构出想要的东西了。

    const [mutation, setMutation] = useState(0) // 纯数字作为初始值

    const [mutation, setMutation] = useState(()=>handle(1)) // 函数作为初始值,函数的返回值作为初始值

    这里我们遗留了一个问题

    • 我们知道第二个参数dispatch执行的时候会触发渲染更新,以及二次更新,那么他是怎么实现的呢?

    针对于上述问题,我们必须去看一下dispatch到底干了什么事情。

    const dispatch: Dispatch<
        BasicStateAction<S>,
      > = (queue.dispatch = (dispatchAction.bind(
        null,
        currentlyRenderingFiber,
        queue,
      ): any));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    dispatchAction

    function dispatchAction<S, A>(
      fiber: Fiber,
      queue: UpdateQueue<S, A>,
      action: A,
    ) {
      if (__DEV__) {
        ...
      }
      // 获取触发更新的时间
      const eventTime = requestEventTime();
      // 获取更新优先级
      const lane = requestUpdateLane(fiber);
    
      // 创建更新
      const update: Update<S, A> = {
        lane,
        action,
        eagerReducer: null,
        eagerState: null,
        next: (null: any),
      };
    
      // 维护链表,在update阶段,如果有更新任务则一直延续
      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;
    
      const alternate = fiber.alternate;
    
      // 如果是render阶段更新
      if (
        fiber === currentlyRenderingFiber ||
        (alternate !== null && alternate === currentlyRenderingFiber)
      ) {
        ...
        didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
    
      } else {
        if (
          fiber.lanes === NoLanes &&
          (alternate === null || alternate.lanes === NoLanes)
        ) {
    
          // 当前没有更新,是第一次调用dispatchAction,计算state
          const lastRenderedReducer = queue.lastRenderedReducer;
          if (lastRenderedReducer !== null) {
            let prevDispatcher;
            ...
            try {
              const currentState: S = (queue.lastRenderedState: any);
              // render阶段,如果reducer没有变化,直接取值eagerState
              const eagerState = lastRenderedReducer(currentState, action);
    
              // render阶段,如果reducer没有变化,直接取值eagerState
              update.eagerReducer = lastRenderedReducer;
              update.eagerState = eagerState;
              if (is(eagerState, currentState)) {
                // 如果当前的值遇前面的值相同直接返回上一个值,不发起更新
                return;
              }
            } catch (error) {
              ...
            } finally {
              ...
            }
          }
        }
        ...
    
        //更新
        scheduleUpdateOnFiber(fiber, lane, eventTime);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    这里我们可以看到usetate的回调,就是创建更新,维护了一个链表结构,在render阶段我们根据eagerState与这次的currentState比较,决定要不要发起更新,执行scheduleUpdateOnFiber进行更新

    函数组件Update阶段

    const HooksDispatcherOnUpdate: Dispatcher = {
      useCallback: updateCallback,
      useContext: readContext,
      useEffect: updateEffect,
      useImperativeHandle: updateImperativeHandle,
      useLayoutEffect: updateLayoutEffect,
      useMemo: updateMemo,
      useReducer: updateReducer,
      useRef: updateRef,
      useState: updateState,
      ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    update阶段中的useState调用了updateState,而在updateState中再次调用updateReducer

    function updateState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      //   
      return updateReducer(basicStateReducer, (initialState: any));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    basicStateReducer中进行了如下处理:

    function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
      // $FlowFixMe: Flow doesn't like mixed types
      // 如果更新为函数则是函数执行返回值为更新值
      return typeof action === 'function' ? action(state) : action;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里也就是说明了我们在写hook的时候:

    setMutation(mutation + 1) // 直接赋值

    setMutation(()=>handle(1)) // 函数值返回值作为新值赋值

    useReducer

    function updateReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
    
      // 获取更新的hook
      const hook = updateWorkInProgressHook();
      const queue = hook.queue;
      invariant(
        queue !== null,
        'Should have a queue. This is likely a bug in React. Please file an issue.',
      );
    
      // 更新
      queue.lastRenderedReducer = reducer;
    
      const current: Hook = (currentHook: any);
    
      // The last rebase update that is NOT part of the base state.
      let baseQueue = current.baseQueue;
    
      // 如果最后一个更新还没有完毕,则继续更新
      const pendingQueue = queue.pending;
      if (pendingQueue !== null) {
    
        // 如果在更新的过程中有新的更新,加入更新队列
        if (baseQueue !== null) {
          const baseFirst = baseQueue.next;
          const pendingFirst = pendingQueue.next;
          baseQueue.next = pendingFirst;
          pendingQueue.next = baseFirst;
        }
        if (__DEV__) {
          if (current.baseQueue !== baseQueue) {
            // Internal invariant that should never happen, but feasibly could in
            // the future if we implement resuming, or some form of that.
            console.error(
              'Internal error: Expected work-in-progress queue to be a clone. ' +
                'This is a bug in React.',
            );
          }
        }
        current.baseQueue = baseQueue = pendingQueue;
        queue.pending = null;
      }
    
      if (baseQueue !== null) {
        // We have a queue to process.
        const first = baseQueue.next;
        let newState = current.baseState;
    
        let newBaseState = null;
        let newBaseQueueFirst = null;
        let newBaseQueueLast = null;
        let update = first;
    
        do {
          //获取每一个任务的更新优先级
          const updateLane = update.lane;
    
          // 如果当前的执行任务优先级不高,则跳过当前任务,去执行优先级高的任务
          if (!isSubsetOfLanes(renderLanes, updateLane)) {
            // Priority is insufficient. Skip this update. If this is the first
            // skipped update, the previous update/state is the new base
            // update/state.
    
            // 克隆更新任务
            const clone: Update<S, A> = {
              lane: updateLane,
              action: update.action,
              eagerReducer: update.eagerReducer,
              eagerState: update.eagerState,
              next: (null: any),
            };
            if (newBaseQueueLast === null) {
              newBaseQueueFirst = newBaseQueueLast = clone;
              newBaseState = newState;
            } else {
              newBaseQueueLast = newBaseQueueLast.next = clone;
            }
    
            // 如果都是低级任务,则合并优先级
            currentlyRenderingFiber.lanes = mergeLanes(
              currentlyRenderingFiber.lanes,
              updateLane,
            );
            markSkippedUpdateLanes(updateLane);
          } else {
            // This update does have sufficient priority.
    
            if (newBaseQueueLast !== null) {
              // 如果更新队列还有更新任务
              const clone: Update<S, A> = {
                // This update is going to be committed so we never want uncommit
                // it. Using NoLane works because 0 is a subset of all bitmasks, so
                // this will never be skipped by the check above.
                lane: NoLane,
                action: update.action,
                eagerReducer: update.eagerReducer,
                eagerState: update.eagerState,
                next: (null: any),
              };
              // 把更新任务插入到队列末尾
              newBaseQueueLast = newBaseQueueLast.next = clone;
            }
    
            // 执行每一个reducer获取newState
            // 如果每一次的reducer相同,则把eagerState赋给newState
            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);
            }
          }
          // 继续更新
          update = update.next;
        } while (update !== null && update !== first);
    
        if (newBaseQueueLast === null) {
          newBaseState = newState;
        } else {
          newBaseQueueLast.next = (newBaseQueueFirst: any);
        }
    
        // 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.baseState = newBaseState;
        hook.baseQueue = newBaseQueueLast;
    
        queue.lastRenderedState = newState;
      }
    
      const dispatch: Dispatch<A> = (queue.dispatch: any);
      return [hook.memoizedState, dispatch];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146

    所以,useState大致的执行流程如下:

    在这里插入图片描述

    既然useReduceruseState同为状态钩子,那就来看一看userReducer的实现吧

    useReducer的用法

    import React, {useReducer} from 'react';
    const initialState = {num: 0};
    const reducer = (state, action) => {
        switch(action.type){
            case 'ad': 
                return {num: state.num + 1};
            case 'de': 
                return {num: state.num - 1};
            case 'ch': 
                return {num: state.num * 2};
            case 'dv': 
                return {num: state.num / 2};    
            default:
               return state
        }
    }
    
    const App = () => {
        const [state, dispatch] = useReducer(reducer, initialState);
        retrun (
            <div className="App">
              <h1>{state.num}</h1>
              <div onClick={() => dispatch({type:ad})}>ad</div>
              <div onClick={() => dispatch({type:de})}>de</div>
              <div onClick={() => dispatch({type:ch})}>ch</div>
              <div onClick={() => dispatch({type:dv})}>dv</div>
            </div>
        )
    }
    
    export default App
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    各位可以自行去codeSandBox上面去测试玩耍。

    分析上述代码我们可以看到每一个div的点击会绑定一个dispatch,这个dispatch可以实现useState里面的setMutation的功能,维护的state值来自于initialStateactiondispatch的入参,针对于这一些,我们去看看源码。

    useReducer的mount阶段

    我们根据HooksDispatcherOnMount可以找到在mount阶段,useReducer调用的是mountReducer

    mountReducer

    function mountReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      //关联链表
      const hook = mountWorkInProgressHook();
      let initialState;
      if (init !== undefined) {
        initialState = init(initialArg);
      } else {
        initialState = ((initialArg: any): S);
      }
      // 保存初始值
      hook.memoizedState = hook.baseState = initialState;
    
      //更新队列
      const queue = (hook.queue = {
        pending: null,
        dispatch: null,
        lastRenderedReducer: reducer,
        lastRenderedState: (initialState: any),
      });
      // 处理回调
      const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
        null,
        currentlyRenderingFiber,
        queue,
      ): any));
      //返回hook.memoizedState与回调
      return [hook.memoizedState, dispatch];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    其实这个函数与useState非常类似,只不过他的第一入参为reducer,并且附带了一个可选参数init,官网上面对其称之为惰性初始化。update阶段调用的是updateReducer,在这里不再复述。

    生命周期相关的hook

    生命周期相关的这里讲解两个useEffectuseLayoutEffect,面试中经常会问到这两个有什么区别,其实他们并没有什么区别,只是执行的时机不同,具体表现为:

    • useLayoutEffect是在渲染的时候同步执行的,且与componentDidMountcomponentDidUpdate执行时机一致,所以在commit阶段中,执行于commitLayoutEffec函数里。
    • useEffect执行是异步的,要等到组件渲染到屏幕上才会去执行。

    操作dom性能相关问题为什么修改dom建议放在useLayoutEffect中而不是useEffect中呢?

    • 从上述表述中可以看出,只要真实dom被创建,就会执行useLayoutEffect函数,而我们知道在创建真实dom的时候,节点的属性,样式等等都会被挂到dom节点上面去,一个节点增加属性必定会引起页面的重绘重排。而useEffect执行时机实在dom渲染到屏幕上面之后,这个时候已经经历过了一次重绘重排。如果再一次的修改dom必定引起二次重绘重排,这样来说,性能开销变成了两倍。所以为了减少性能开销,才如此建议。废话少说来看看useEffect的具体实现,mount阶段对应的是mountEffect

    mountEffect

    function mountEffect(
      // 传入的函数useEffect(()=>{},[])
      create: () => (() => void) | void,  // 依赖
      deps: Array<mixed> | void | null,
    ): void {
      if (__DEV__) {
         ...
        }
      }
      return mountEffectImpl(
        UpdateEffect | PassiveEffect,
        HookPassive,
        create,
        deps,
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    mountEffect调用mountEffectImpl

    mountEffectImpl

    function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
      const hook = mountWorkInProgressHook();// 关联链表
      const nextDeps = deps === undefined ? null : deps;//处理依赖
      currentlyRenderingFiber.flags |= fiberFlags;// 关联副作用标记
      // 加入到环形链表中
      hook.memoizedState = pushEffect(
        HookHasEffect | hookFlags,
        create,
        undefined,
        nextDeps,
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    pushEffect

    function pushEffect(tag, create, destroy, deps) {
      // 入参为副作用标记,传入函数,销毁回调,依赖项
      const effect: Effect = {
        tag,
        create,
        destroy,
        deps,
        // Circular
        // 下一个副作用
        next: (null: any),
      };
    
      //获取函数组件更新队列
      let componentUpdateQueue: null | FunctionComponentUpdateQueue = 
      (currentlyRenderingFiber.updateQueue: any);
    
      if (componentUpdateQueue === null) {
        //队列为空,创建队列
        componentUpdateQueue = createFunctionComponentUpdateQueue();
        //与fiber更新队列关联起来
        currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
        componentUpdateQueue.lastEffect = effect.next = effect;
    
      } else {
        //有更新队列
        const lastEffect = componentUpdateQueue.lastEffect;
        if (lastEffect === null) {
          componentUpdateQueue.lastEffect = effect.next = effect;
        } else {
          // 处理关联链表
          const firstEffect = lastEffect.next;
          lastEffect.next = effect;
          effect.next = firstEffect;
          componentUpdateQueue.lastEffect = effect;
        }
      }
      return effect;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    这里做的事情,无非就是关联componentUpdateQueueeffect,让React去调度执行。

    updateEffect

    updateEffect执行调用了updateEffectImpl

    // updateEffect
    function updateEffect(
      // 传入函数
      create: () => (() => void) | void,  // 依赖项
      deps: Array<mixed> | void | null,
    ): void {
      if (__DEV__) {
        ...
      }
      return updateEffectImpl(
        UpdateEffect | PassiveEffect,
        HookPassive,
        create,
        deps,
      );
    }
    
    //updateEffectImpl
    function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
      const hook = updateWorkInProgressHook();// 获取更新hook
      const nextDeps = deps === undefined ? null : deps; //依赖项
    
      let destroy = undefined; //销毁函数
    
      if (currentHook !== null) {
        const prevEffect = currentHook.memoizedState;
        destroy = prevEffect.destroy;
        if (nextDeps !== null) {
          const prevDeps = prevEffect.deps;
          // 比较这一次的依赖与上一次的依赖
          if (areHookInputsEqual(nextDeps, prevDeps)) {
            // 即使依赖相同,也要讲effect加入链表,保证顺序
            pushEffect(hookFlags, create, destroy, nextDeps);
            return;
          }
        }
      }
    
      currentlyRenderingFiber.flags |= fiberFlags;
    
      //在commit阶段处理effect
      hook.memoizedState = pushEffect(
        HookHasEffect | hookFlags,
        create,
        destroy,
        nextDeps,
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    本着学习到底的精神,我们一起来看看比较两次的依赖,到底是怎么比较的:

    areHookInputsEqual

    function areHookInputsEqual(
      nextDeps: Array<mixed>, //当前的依赖
      prevDeps: Array<mixed> | null,// 上一次的依赖
    ) {
      if (__DEV__) {
       ...
      }
    
      // 上一次为空,肯定不一样,则必须更新
      if (prevDeps === null) {
        if (__DEV__) {
         ...
        }
        return false;
      }
    
      if (__DEV__) {
        if (nextDeps.length !== prevDeps.length) {
         ...
      }
      // 遍历两个依赖数组,根据下标比较
      for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
        if (is(nextDeps[i], prevDeps[i])) {
          continue;
        }
        return false;
      }
      return true;
    }
    
    // import is form 'shared/objectIs'
    const objectIs: (x: any, y: any) => boolean =
      // 兼容Object.is
      typeof Object.is === 'function' ? Object.is : is;
    
    // is 
    function is(x: any, y: any) {
      return (
        (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) 
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    上述代码介绍了比较两种值是否一样,其中Object.is的浏览器适用版本比较高。做了一次兼容:

    上面说了useEffectuseLayoutEffect的结构都是一样的,重点只是在于他们俩的执行时机不一样,那么我们就去深究一下useEffectuseLayoutEffect的执行时机。

    生命周期相关的hook的执行时机

    我们知道所谓生命周期钩子,那肯定是在某一阶段去执行的,这个阶段就是commit阶段。在commit阶段的commitLayoutEffects函数中执行一系列的生命周期钩子,但是对于函数组件来讲,会调度useEffectcreatedestroy,也就是执行schedulePassiveEffects函数。

    function schedulePassiveEffects(finishedWork: Fiber) {
      // 获取更新队列
      const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
      // 获取最后一个effect
      const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
      //如果队列没有执行完
      if (lastEffect !== null) {
        //把队列里面的最后一个作为开头
        const firstEffect = lastEffect.next;
        let effect = firstEffect;
        do {
          const {next, tag} = effect;
          if (
            (tag & HookPassive) !== NoHookEffect &&
            (tag & HookHasEffect) !== NoHookEffect
          ) {
            // 将effect中的destroy和create加入到e
            //pendingPassiveHookEffectsUnmount
            //pendingPassiveHookEffectsMount中
            enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
            enqueuePendingPassiveHookEffectMount(finishedWork, effect);
          }
          effect = next;
        } while (effect !== firstEffect);
      }
    }
    
    // enqueuePendingPassiveHookEffectMount
    export function enqueuePendingPassiveHookEffectMount(
      fiber: Fiber,  effect: HookEffect,
    ): void {
      pendingPassiveHookEffectsMount.push(effect, fiber);
      if (!rootDoesHavePassiveEffects) {
        // root上有没有副作用,默认值为false
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalSchedulerPriority, () => {
          flushPassiveEffects();
          return null;
        });
      }
    }
    
    // enqueuePendingPassiveHookEffectUnmount
    export function enqueuePendingPassiveHookEffectUnmount(
      fiber: Fiber,  effect: HookEffect,
    ): void {
      pendingPassiveHookEffectsUnmount.push(effect, fiber);
      if (__DEV__) {
        fiber.flags |= PassiveUnmountPendingDev;
        const alternate = fiber.alternate;
        if (alternate !== null) {
          alternate.flags |= PassiveUnmountPendingDev;
        }
      }
      if (!rootDoesHavePassiveEffects) {
        // root上有没有副作用,默认值为false
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalSchedulerPriority, () => {
          flushPassiveEffects();
          return null;
        });
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    调度执行flushPassiveEffects

    调度执行flushPassiveEffect会把当前正在执行的任务优先级跟后来新增的优先级进行比较。

    export function flushPassiveEffects(): boolean {
        ...
        if (decoupleUpdatePriorityFromScheduler) {
          // 获得更新优先级
          const previousLanePriority = getCurrentUpdateLanePriority();
          try {
            ...
            return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
          } finally {
            ...
          }
        } else {
          return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
        }
      }
      return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    flushPassiveEffectsImpl

    function flushPassiveEffectsImpl() {
    
      // First pass: Destroy stale passive effects.
      const unmountEffects = pendingPassiveHookEffectsUnmount;
      pendingPassiveHookEffectsUnmount = [];
      for (let i = 0; i < unmountEffects.length; i += 2) {
        const effect = ((unmountEffects[i]: any): HookEffect);
        const fiber = ((unmountEffects[i + 1]: any): Fiber);
        const destroy = effect.destroy;
        effect.destroy = undefined;
    
        ...
        if (typeof destroy === 'function') {
          ...
          } else {
            try {
              if (
                ...
              ) {
                try {
                  startPassiveEffectTimer();
                  destroy();//销毁回调执行
                } finally {
                  recordPassiveEffectDuration(fiber);
                }
              } else {
                destroy();//销毁回调执行
              }
            } catch (error) {
              ...
            }
          }
        }
      }
      // Second pass: Create new passive effects.
      const mountEffects = pendingPassiveHookEffectsMount;
      pendingPassiveHookEffectsMount = [];
      for (let i = 0; i < mountEffects.length; i += 2) {
        const effect = ((mountEffects[i]: any): HookEffect);
        const fiber = ((mountEffects[i + 1]: any): Fiber);
        if (__DEV__) {
          ...
        } else {
          try {
            const create = effect.create;
            if (
              ...
            ) {
              try {
                startPassiveEffectTimer();
                effect.destroy = create();//本次的render的创建函数
              } finally {
                recordPassiveEffectDuration(fiber);
              }
            } else {
              effect.destroy = create();//本次的render的创建函数
            }
          } catch (error) {
            ...
          }
        }
      }
      ...
      return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    在这个函数里面就是依次执行上一次的render销毁回调函数、本次的render创建回调函数。而在前面也说过commit流程是无法中断的,只有等所有节点全部commit完,浏览器才会去告知react可以执行自己的调度任务了,也正在此刻useEffect所对应的函数才会去执行,

    在生命周期hook里面React帮我们做了一定的性能优化,除了这个还提供了几个手动优化hookuseMemouseCallback,那我们来一起瞧瞧吧。

    性能优化相关hook

    在讲这一部分之前我们应该搞明白一个问题

    • 为什么要用useMemouseCallback来做性能优化
    • useMemouseCallback应该怎么做性能优化
    • 具体场景是什么

    先回答第一个问题,为什么要用他们来做性能优化: 我们要知道,React更新流程中只要组件中props或者state发生了变化,那就是必须从变化顶部更新所有的后代组件,牵一发而动全身。有些组件我们并不想要它重新渲染,它却做了一定量的性能牺牲,用useMemouseCallback就可以改变这样的局面。那么应该怎么用他们来做性能优化呢,主要体现在三个方面:

    • 避免无效的副作用
    • 避免无效的累计计算
    • 避免无效的重新渲染

    因为我们前面讲到useEffect,我们可以期望在依赖发生变更的时候去做我们想要做的事情,例如接口请求

    const App = () => {
      // 只有当parmas发生变化的时候,我们才回去执行fetch()
      useEffect(()=>{
        fetch()
      }, [params])
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这并不是他们俩的功能呢,没关系,我们可以变着法子让他们俩具有类似功能,不过这就有点牵强了。
    那怎么去做无效的计算和无效的重复渲染呢?有这样的一道面试题:

    // 点击父组件里面的按钮,会不会在子组件里面打印“子组件渲染了”?如果会,该怎么优化?
    import { useState } from "react";
    
    // 父组件
    export const Parent = () => {
      console.log("父组件渲染了");
      const [count, setCount] = useState(0);
      const increment = () => setCount(count + 1);
    
      return (
        <div>
          <button onClick={increment}>点击次数:{count}</button>
          <Child />
        </div>
      );
    };
    
    // 子组件
    export const Child = ({}) => {
      console.log("子组件渲染了");
      return <div>子组件</div>;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    很明显点击父组件,改变了count,整个组件都要重新渲染。肯定会打印的,那怎么优化呢?

    import { useState, useMemo } from "react";
    
    // 父组件
    export const Parent = () => {
      console.log("父组件渲染了");
      const [count, setCount] = useState(0);
      const increment = () => setCount(count + 1);
    
      return (
        <div>
          <button onClick={increment}>点击次数:{count}</button>
          // 这种写法不太提倡,一般采用的是memo      {useMemo(()=>(),[])}    
    ); }; // 子组件 export const Child = ({}) => { console.log("子组件渲染了"); return <div>子组件</div>; }; //export const Child = memo(({}) => { // console.log("子组件渲染了"); // return
    子组件
    ;
    //});
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    那么避免无效的计算体现在哪里呢:

    import { useState } from "react";
    
    const App = () => {
      const [x, setX] = useState(0);
      const [y, setY] = useState(1);
    
      const caculate = () => {
        console.log('正在计算中')
        //庞大的计算,跟x相关的计算
        return x
      }
    
      return (
         <div>
            <h4>caculate:{caculate}</h4>
            <h4>x:{x}</h4>
            <h4>y:{y}</h4>
            <div onClick={()=>setX(x + 1)}>x+1</div>
            <div onClick={()=>setY(y + 1)}>y+1</div>
          </div>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    上面的代码里面的函数caculate只是一个与x有关的处理逻辑,与y没有关系,但是我们知道,只要触发了render,那么整个脚本就会自上而下执行一遍,很明显,如果这里只触发setY的话,他也会重新执行一遍脚本,而在caculate里面具有大量的耗时计算,那么这个时候,再次重新触发就显得很没有必要,故而我们可以采用useMemo来进行优化。

    import { useState } from "react";
    
    const App = () => {
      const [x, setX] = useState(0);
      const [y, setY] = useState(1);
    
      const caculate = useMemo(() => {
        console.log('正在计算中')
        //庞大的计算,跟x相关的计算
        return x
      },[x]) // 只有x变化的时候,才会重新执行caculate
    
      return (
         <div>
            <h4>caculate:{caculate}</h4>
            <h4>x:{x}</h4>
            <h4>y:{y}</h4>
            <div onClick={()=>setX(x + 1)}>x+1</div>
            <div onClick={()=>setY(y + 1)}>y+1</div>
          </div>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    useCallback适用于父子组件嵌套,父组件基于属性把方法传递给子组件,保证了每一次父组件更新不会重新创建函数堆,而是获取之前的引用,传递给子组件的属性就没有变化,例如:

    // 父组件
    import Child from './Child'
    const Parent = () => {
      console.log('父组件渲染了')
      const [info, setInfo] = useState({name:'一溪之石', age: 18});
      const [count, setCount] = useState(1);
    
      const tansfromChild = () => {
        //....
      }
    
      //采用useCallback包裹
      //const tansfromChild = useCallback(() => {
      //  //....
      //})
    
      const handleSome = () => {
        setCount(count + 1)
      }
      return (
        <>
          <div onClick={() => handleSome}>父组件</div>
          <Child tansfromChild={tansfromChild} info={info}/>
        </>
      )
    }
    
    // 子组件
    const Child = ({tansfromChild, info}) => {
      console.log('子组件渲染了')
      return (
        <div onClick={() => tansfromChild}>{info.name}{info.age}</div>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    上述代码中点击父组件的函数,必定会使得count + 1,从而会重新渲染父组件,重新创建函数(不考虑info),对于子组件来说,props改变,就一定会发生重新渲染。针对这种情况,我们一般采用用useCallback包裹起来,然后你去执行点击父组件的函数,你发现他依旧是会重新渲染子组件。因为子组件是函数组件。他没有处理props浅比较,故而每一次的props他都是不一样的。针对于这种问题解决方式:

    • 子组件写成class组件,继承PureComponent(不推荐)
    • 子组件写成函数组件,用memo包裹起来。

    因为PureComponent里面会有一个shouldComponentUpdate来处理props的浅比较,而memo则也是我们现在需要去探索的东西。所以对于useCallback的使用一定要配合上具有浅比较的组件使用,否则不能优化性能,反而浪费性能。

    useMemo适用于大量的计算得出的结果,防止在组建更新的时候,避免不必要的重新计算

    说了这么多,我们还是一起来看看他们是如何实现的吧,根据上面的经验,我们可以在HooksDispatcherOnMount里面找到mountCallbackmountMemo

    mountCallback、mountMemo

    // mountCallback
    function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
      const hook = mountWorkInProgressHook(); // 获取hook链表,并关联
      const nextDeps = deps === undefined ? null : deps; // 依赖
      hook.memoizedState = [callback, nextDeps]; // 存入memoizedState
      return callback; // 返回传入的函数
    }
    
    // mountMemo
    function mountMemo<T>(
      nextCreate: () => T,
      deps: Array<mixed> | void | null,
    ): T {
      const hook = mountWorkInProgressHook(); // 获得hook,关联链表
      const nextDeps = deps === undefined ? null : deps; // 依赖
      const nextValue = nextCreate(); // 执行传入函数,把值给nextValue
      hook.memoizedState = [nextValue, nextDeps]; // 把值和依赖加入到memoizedState中
      return nextValue; // 把传入函数的执行返回值,返回
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上述代码简单如斯,mountCallback直接把传入的函数返回出去了,而mountMemo会把传入的函数执行,把返回值返回出去。并没有什么好讲的,我们看看他们怎么更新吧。

    updateCallback、updateMemo

    // updateCallback
    function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
      const hook = updateWorkInProgressHook(); // 获取更新hook
      const nextDeps = deps === undefined ? null : deps; // 依赖
      // [callback, nextDeps]上一个的hook.memoizedState
      const prevState = hook.memoizedState; 
    
      if (prevState !== null) { // 上一个hook.memoizedState不为空
        if (nextDeps !== null) { // 这次也不为空
    
          const prevDeps: Array<mixed> | null = prevState[1]; 
          // [callback, nextDeps]取出依赖赋给prevDeps
    
          if (areHookInputsEqual(nextDeps, prevDeps)) { 
          // 如果依赖一样,则返回上一次的函数(缓存函数)
            return prevState[0];
          }
        }
      }
      // 如果上一次没有,表示首次渲染,则记录函数与依赖项,并返回传入的函数
      hook.memoizedState = [callback, nextDeps]; 
      return callback;
    }
    
    //updateMemo
    function updateMemo<T>(
      nextCreate: () => T,
      deps: Array<mixed> | void | null,
    ): T {
      const hook = updateWorkInProgressHook(); // 获取更新hook
      const nextDeps = deps === undefined ? null : deps; //依赖
    
      const prevState = hook.memoizedState; // 获取上一次的值(缓存值)[nextValue, nextDeps]
    
      if (prevState !== null) {
        // Assume these are defined. If they're not, areHookInputsEqual will warn.
        if (nextDeps !== null) {
          const prevDeps: Array<mixed> | null = prevState[1]; 
          // 比较两次依赖
          if (areHookInputsEqual(nextDeps, prevDeps)) {
            return prevState[0]; //如果相等就返回上一次的计算结果值
          }
        }
      }
      // 首次渲染,执行会犯传入函数的结果
      const nextValue = nextCreate();
      hook.memoizedState = [nextValue, nextDeps];// 存入hook.memoizedState
      return nextValue; // 返回计算结果
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    两个函数的更新也是异曲同工,细节都写在了代码里面,也没什么好讲的。

    useContext

    开始看useContext之前,我们要明白这个hook可以用来干什么。

    接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 value prop 决定。

    • useContext可以帮助我们跨越组件层级直接传递变量,实现数据共享。
    • Context的作用就是对它所包含的组件树提供全局共享数据的一种技术。

    听的云里雾里的?不要担心,说白了就是组件之间传值就完事了,那么你又说了,组件传值我们用props传递不就完毕了吗,为什么还要有这个,太天真。useContext提供的功能是可以跨层级的。
    假如有ABCDE五个组件嵌套,你用props传值,是不是要A传到B…一直传到E,但是useContext就可以在E组件里面直接获取到A的状态,举个例子:

    const ComponentA = () => {
      const [count, setCount] = useState(0);
      return (
        <div>
          <h4>父级:{count}</h4>
          <ComponentB count={count}/>
        </div>
      )
    }
    
    const ComponentB = (count) => {
      return (
        <div>
          <h4>父级:{count}</h4>
          <ComponentC count={count} />
        </div>
      )
    }
    
    const ComponentC = (count) => {
      return (
        <div>
          <h4>父级:{count}</h4>
          <ComponentD count={count} />
        </div>
      )
    }
    
    const ComponentD = (count) => {
      return (
        <div>
          <h4>父级:{count}</h4>
          <ComponentE count={count}/>
        </div>
      )
    }
    
    const ComponentE = (count) => {
      return (
        <div>
          <h4>来自A父级的count:{count}</h4>
        </div>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    这样才能在E组件里面拿到来自A组件的count,但是useContext提供了,直接拿的功能,例如:

    // 用createContext创建上下文
    const Content = createContext(null);
    
    // 在父组件里面用Content.Provider包裹子组件,指定上下文范围
    const ComponentA = () => {
      const [count, setCount] = useState(0);
      return (
        <div>
          <h4>父级:{count}</h4>
          <Content.Provider value={{count,setCount}}>
            <ComponentB />
          </Content.Provider>
        </div>
      )
    }
    ...
    
    const ComponentE = (count) => {
      // 需要用到的组件里面用useContext接收
      const [count, setCount] = useContext(Content)
      return (
        <div>
          <h4>来自A父级的count:{count}</h4>
        </div>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    所以useContext的用法无非就三点:

    • createContext创建上下文
    • Content.Provider指定上下文
    • useContext使用上下文

    既然知道他的用法,那么一起瞧瞧他的实现吧,首先我们肯定要去关注一下createContext

    createContext

    export function createContext<T>(
      defaultValue: T, // 传入的默认值
      calculateChangedBits: ?(a: T, b: T) => number, // 可选参数处理默认值的变化
    ): ReactContext<T> {
      // 没有传入处理函数
      if (calculateChangedBits === undefined) {
        calculateChangedBits = null;
      } else {
        ...
      }
    
      // context对象
      const context: ReactContext<T> = {
        $$typeof: REACT_CONTEXT_TYPE,
        _calculateChangedBits: calculateChangedBits,
        _currentValue: defaultValue,
        _currentValue2: defaultValue,
        _threadCount: 0,
        Provider: (null: any),
        Consumer: (null: any),
      };
    
      //context.Provider
      context.Provider = {
        $$typeof: REACT_PROVIDER_TYPE, // element类型
        _context: context,
      };
    
      let hasWarnedAboutUsingNestedContextConsumers = false;
      let hasWarnedAboutUsingConsumerProvider = false;
      let hasWarnedAboutDisplayNameOnConsumer = false;
    
      if (__DEV__) {
        ...
        context.Consumer = Consumer; // dev环境下Consumer就是Consumer
      } else {
        context.Consumer = context;// 正常情况下,Consumer用来包裹接收状态的组件的
      }
      ...
      return context;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    上面的代码主要关注于context.Providercontext.Consumer,先来解释一下他们两个的作用吧。

    • context.Provider的作用在于规定上下文范围,并提供组件所需要的状态。
    • context.Consumer的作用在于接收传输过来的状态。

    eg:

    // ComponentA
    const ComponentA = () => {
      const [count, setCount] = useState(0);
      return (
        <div>
          <h4>父级:{count}</h4>
          <Content.Provider value={{count,setCount}}>
            <ComponentB />
          </Content.Provider>
        </div>
      )
    }
    
    // ComponentB
    const ComponentB = (count) => {
      // 需要用到的组件里面用useContext接收
      // const [count, setCount] = useContext(Content)
      return (
        <div>
          <context.Consumer>
            {(count)=><h4>来自A父级的count:{count}</h4>}      </context.Consumer>
        </div>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    可见不用从useContext中解构出来,而用包裹起来,也是可以达到一样的效果的,他们之间的关系大致可以这样表示:

    在这里插入图片描述

    useContext的执行流程

    谈到useContext这里就不得不跟react里面的context一起说一下,在react源码中存在一个valueStackvalueCursor用来记录context的历史信息和当前contextdidPerformWorkStackCursor用来表示当前的context有没有变化。

    //ReactFiberNewContext.old.js
    const valueCursor: StackCursor<mixed> = createCursor(null);
    
    const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
    
    • 1
    • 2
    • 3
    • 4
    //ReactFiberStack.old.js
    const valueStack: Array<any> = [];
    
    • 1
    • 2
    • render阶段的beginWork函数里调用updateContextProvider的时候会执行pushProvider,将新的值pushvalueStack
    • render阶段的completeWork函数里调用popProvider,将栈顶context pop出来
    //pushProvider
    function pushProvider(providerFiber, nextValue) {
      var context = providerFiber.type._context;
      {
        push(valueCursor, context._currentValue, providerFiber);
        context._currentValue = nextValue;
      }
    }
    
    //popProvider
    function popProvider(providerFiber) {
      var currentValue = valueCursor.current;
      pop(valueCursor, providerFiber);
      var context = providerFiber.type._context;
    
      {
        context._currentValue = currentValue;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    因为在render阶段,会以深度优先的方式遍历节点,这样在每一层级都可以通过valueCursor拿到最新的值了。上面也讲了createContext的实现,那么在使用的时候useContext又在干了什么呢?我们通过调用可以看到是调用了readContext,而readContext创建了 dependencies并关联到了fiberdependencies上面。更新阶段再次调用readContext去根据context的变化执行调度更新。

    export function readContext<T>(
      context: ReactContext<T>,
      observedBits: void | number | boolean,
    ): T {
      ...
    
      if (lastContextWithAllBitsObserved === context) {
        // Nothing to do. We already observe everything in this context.
    
        // 走mount阶段的useContext逻辑
      } else if (observedBits === false || observedBits === 0) {
        // Do not observe any updates.
      } else {
    
        // 生成resolvedObservedBits
        let resolvedObservedBits; 
        // Avoid deopting on observable arguments or heterogeneous types.
        if (
          typeof observedBits !== 'number' ||
          observedBits === MAX_SIGNED_31_BIT_INT
        ) {
          // Observe all updates.
          lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
          resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
        } else {
          resolvedObservedBits = observedBits;
        }
    
        // 生成dependencies
        const contextItem = {
          context: ((context: any): ReactContext<mixed>),
          observedBits: resolvedObservedBits,
          next: null,
        };
    
        if (lastContextDependency === null) {
          invariant(
            ...
          );
    
          // This is the first dependency for this component. Create a new list.
          lastContextDependency = contextItem;
          // fiber dependencies链表的第一个context
          currentlyRenderingFiber.dependencies = {
            lanes: NoLanes,
            firstContext: contextItem,
            responders: null,
          };
        } else {
          // Append a new context item. 
          // 加入dependencies
          lastContextDependency = lastContextDependency.next = contextItem;
        }
      }
      return isPrimaryRenderer ? context._currentValue : context._currentValue2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    我们再来看一看render阶段react会根据不同的组件类型去执行updateContextProviderupdateContextConsumer

    updateContextProvider

    function updateContextProvider(
      current: Fiber | null,  workInProgress: Fiber,  renderLanes: Lanes,
    ) {
      const providerType: ReactProviderType<any> = workInProgress.type;
      const context: ReactContext<any> = providerType._context;
    
      const newProps = workInProgress.pendingProps; // 新的状态
      const oldProps = workInProgress.memoizedProps;// 上一次的状态
    
      const newValue = newProps.value;// 新状态的value
      ...
    
      // 把value加入到valueStack中
      pushProvider(workInProgress, newValue);
    
      if (oldProps !== null) {
        const oldValue = oldProps.value;
    
        // 计算context变化的值
        const changedBits = calculateChangedBits(context, newValue, oldValue);
        if (changedBits === 0) { // context没有变化
          // No change. Bailout early if children are the same.
          if (
            oldProps.children === newProps.children &&
            !hasLegacyContextChanged()
          ) {
            return bailoutOnAlreadyFinishedWork(
              current,
              workInProgress,
              renderLanes,
            );
          }
        } else { // context有变化了
          // The context value changed. Search for matching consumers and schedule
          // them to update.
          propagateContextChange(workInProgress, context, changedBits, renderLanes);
        }
      }
    
      const newChildren = newProps.children;
      // context变化,要重新diff
      reconcileChildren(current, workInProgress, newChildren, renderLanes);
      return workInProgress.child;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    上面代码可以看到如果changedBits === 0则表示当前context没有变化。

    calculateChangedBits

    export function calculateChangedBits<T>(
      context: ReactContext<T>,
      newValue: T,
      oldValue: T,
    ) {
      // 两次的props是一样的
      if (is(oldValue, newValue)) {
        // No change
        return 0;
      } else {
        // 两次props不一样
        const changedBits =
          typeof context._calculateChangedBits === 'function'
            ? context._calculateChangedBits(oldValue, newValue)
            : MAX_SIGNED_31_BIT_INT;
    
        ...
        return changedBits | 0; // 返回0或者MAX_SIGNED_31_BIT_INT
        //  MAX_SIGNED_31_BIT_INT = 1073741823,31位调度算法
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    计算完changedBits之后,如果没有变化就执行bailoutOnAlreadyFinishedWork,跳过当前的节点更新。如果有变化了则执行propagateContextChange去更新当前节点信息。

    // bailoutOnAlreadyFinishedWork
    function bailoutOnAlreadyFinishedWork(
      current: Fiber | null,  workInProgress: Fiber,  renderLanes: Lanes,
    ): Fiber | null {
      if (current !== null) {
        // 复用上一次的依赖
        workInProgress.dependencies = current.dependencies;
      }
    
     ...
      // Check if the children have any pending work.
      if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
        ...
        return null;
      } else {
        // 复用孩子节点
        cloneChildFibers(current, workInProgress);
        return workInProgress.child;
      }
    }
    
    // propagateContextChange
    export function propagateContextChange(
      workInProgress: Fiber,  context: ReactContext<mixed>,  changedBits: number,  renderLanes: Lanes,
    ): void {
      // 获得孩子节点信息
      let fiber = workInProgress.child;
    
      if (fiber !== null) {
        // 有孩子的话,就去关联他的父亲
        fiber.return = workInProgress;
      }
      while (fiber !== null) {
        let nextFiber;
    
        // Visit this fiber.
        const list = fiber.dependencies; // 遍历fiber
    
        if (list !== null) {
          nextFiber = fiber.child; // 下一个处理的就是fiber的孩子,深度优先
    
          let dependency = list.firstContext;
          while (dependency !== null) { // 遍历dependency链表
            if (
              // context变化了
              dependency.context === context &&
              (dependency.observedBits & changedBits) !== 0
            ) {
    
              if (fiber.tag === ClassComponent) {
                // 强制调度,创建更新
                const update = createUpdate(
                  NoTimestamp,
                  pickArbitraryLane(renderLanes),
                );
                update.tag = ForceUpdate;
                // 加入更新队列
                enqueueUpdate(fiber, update);
              }
              // 合并context更新任务的优先级与fiber渲染优先级
              fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
              const alternate = fiber.alternate; 
    
              if (alternate !== null) {
                alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
              }
              // 更新爷爷级别的优先级
              scheduleWorkOnParentPath(fiber.return, renderLanes);
    
              ...
              break;
            }
            dependency = dependency.next;
          }
        } else if (fiber.tag === ContextProvider) {
          ...
        } else if (
          enableSuspenseServerRenderer &&
          fiber.tag === DehydratedFragment
        ) {
          ...
        }
        fiber = nextFiber;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    updateContextConsumer

    updateContextConsumer中,执行prepareToReadContext判断优先级是否足够加入当前这次renderreadContext取到当前context的value

    function updateContextConsumer(
      current: Fiber | null,  workInProgress: Fiber,  renderLanes: Lanes,
    ) {
      let context: ReactContext<any> = workInProgress.type;
      ...
      const newProps = workInProgress.pendingProps;
      const render = newProps.children;
    
      ...
      // 比一比谁的优先级高,能不能加入到渲染队列里面去
      prepareToReadContext(workInProgress, renderLanes);
      // 读取新的value
      const newValue = readContext(context, newProps.unstable_observedBits);
    
      let newChildren;
      ...
    
      // React DevTools reads this flag.
      workInProgress.flags |= PerformedWork;
      // diff
      reconcileChildren(current, workInProgress, newChildren, renderLanes);
      return workInProgress.child;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    所以useContext的执行流程大致如下:

    在这里插入图片描述

    属性相关的hook

    对于写原生的朋友来讲,获取一个dom节点直接用document.getElementByXxx,是多么的舒服,react也提供了一种获取节点的hookuseRef eg:

    const App = () => {
      const inputValue = useRef(null);
    
      const getValue = () => {
        console.log(inputValue.current.value)
      }
    
      return (
        <>
          <input ref={inputValue} type="text" />
          <button onClick={getValue}>Click</button>
        </>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    input框中输入文字,点击按钮则可以在控制台获取到输入的文字。对比于原生的,它的优势在于省略掉了那些获取操作。useRef如此简单,还是一起来看一下源码吧。根据以往经验,我们找到mountRef函数。

    mountRef

    function mountRef<T>(initialValue: T): {|current: T|} {
      const hook = mountWorkInProgressHook();// 获取hook链表
    
      const ref = {current: initialValue}; // ref是一个对象 
    
      if (__DEV__) {
        Object.seal(ref);
      }
      // 存入到hook.memoizedState中
      hook.memoizedState = ref;
      // 返回 {current: initialValue} 获取通常用x.current
      return ref;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    更新阶段的useRef

    function updateRef<T>(initialValue: T): {|current: T|} {
      const hook = updateWorkInProgressHook();// 获取更新的hook
      return hook.memoizedState; // hook返回
    }
    
    • 1
    • 2
    • 3
    • 4

    useRef的执行流程

    我们一猜就知道肯定是在某个阶段对具有ref标记的属性标签,进行了某些处理,肯定是render阶段里面做的,我们去renderbeginWork里面去找,果然发现 markRef函数。

    markRef

    const ref = workInProgress.ref;
      if (
        (current === null && ref !== null) ||
        (current !== null && current.ref !== ref)
      ) {
        // Schedule a Ref effect
        workInProgress.flags |= Ref;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    但是在查找的过程中,我们还发现位于completeWork函数中也出现了markRef的身影。

    function markRef(workInProgress: Fiber) {
      workInProgress.flags |= Ref;
    }
    
    • 1
    • 2
    • 3

    在这里面做的事情就是给带有ref属性的标签打上标记,嗯?打上标记干什么呢,那肯定是在commit阶段去处理啊!那么我们再去找一下commit阶段的commitMutationEffects函数,果不其然,

    function commitMutationEffects(
      root: FiberRoot,  renderPriorityLevel: ReactPriorityLevel,
    ) {
        ...
        // flags == 'Ref' ,更新ref current
        if (flags & Ref) {
          const current = nextEffect.alternate;
          if (current !== null) {
            commitDetachRef(current); //移除ref
          }
        }
    
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这个函数里面我们发现了对Ref的处理:

    • 如果Ref改变了,且不为null则执行commitDetachRef删掉ref,之后并在commitLayoutEffect中执行commitAttachRef更新ref的值。

    commitDetachRef

    function commitDetachRef(current: Fiber) {
      const currentRef = current.ref; //获得当前的ref
    
      if (currentRef !== null) {
        // 如果ref是函数则执行
        if (typeof currentRef === 'function') {
          currentRef(null);
        } else {
          // 否则则返回{current:null}
          currentRef.current = null;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    commitAttachRef

    function commitAttachRef(finishedWork: Fiber) {
      const ref = finishedWork.ref;
      if (ref !== null) {
        const instance = finishedWork.stateNode; // 获得ref实例
    
        let instanceToUse;
        switch (finishedWork.tag) {
          case HostComponent:
            instanceToUse = getPublicInstance(instance);
            break;
          default:
            instanceToUse = instance;
        }
    
        // Moved outside to ensure DCE works with this flag
        if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
          instanceToUse = instance;
        }
        if (typeof ref === 'function') {
          // 赋值
          ref(instanceToUse);
        } else {
          ...
          ref.current = instanceToUse;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    所以useRef大致执行流程如下:

    在这里插入图片描述

    总结

    本文主要讲解了部分hooks的使用与原理,对hook使用更加熟悉了,还有一部分React内置hook的使用请查看官网,还有基于React的扩展ahooks都是值得学习的

  • 相关阅读:
    Java反射机制
    canvas 系列学习笔记三《样式和颜色》
    1044 Shopping in Mars(二分)
    最新千万级中文语音语料开源数据整理分享
    LeetCode - 解题笔记 - 215 - Kth Largest Element in an Array
    Ideal maven自己配置的本地仓库无效问题解决
    工作记录-------双 11场景下库存更新 SQL 优化
    JavaScript 64 JavaScript 函数 64.4 JavaScript 函数 Call
    软件建模与分析--共享单车管理系统
    Pandas 全系列教程分享
  • 原文地址:https://blog.csdn.net/weixin_59558923/article/details/128199240