React 暴露出来的部分 Hooks
 //packages/react/src/React.jsexport {  ...  useCallback,  useContext,  useEffect,  useLayoutEffect,  useMemo,  useReducer,  useRef,  useState,  ...}
   复制代码
 功能描述
- useState、- useReducer: 状态值相关
 
- useEffect、- useLayoutEffect: 生命周期相关
 
- useContext: 状态共享相关
 
- useCallback、- useMemo: 性能优化相关
 
- 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);}
// resolveDispatcherfunction resolveDispatcher() {  const dispatcher = ReactCurrentDispatcher.current;  invariant(   ...  );  return dispatcher;}
// ReactCurrentDispatcherconst ReactCurrentDispatcher = {  /**   * @internal   * @type {ReactComponent}   */  current: (null: null | Dispatcher),};
   复制代码
 
其实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,  ...|};
   复制代码
 
很明显这就是各种hooks的定义,但是总归都要是参加到执行的的流程里面去的,函数组件也属于ReactComponent的一种,他也有mount和update阶段。
函数组件 Mount 阶段
我们在前面提到执行beginWork函数中,我们发现会有tag为FunctionComponent的选项,他会调用updateFunctionComponent进而调用renderWithHooks进行更新。
 // packages/react-reconciler/src/ReactFiberBeginWork.old.jsfunction 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:      ...    }}
   复制代码
 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;  }}
   复制代码
 
HooksDispatcherOnMount
 const HooksDispatcherOnMount: Dispatcher = {  useCallback: mountCallback,  useContext: readContext,  useEffect: mountEffect,  useLayoutEffect: mountLayoutEffect,  useMemo: mountMemo,  useReducer: mountReducer,  useRef: mountRef,  useState: mountState,  ...};
   复制代码
 
在mount的情况下,每个hook是都有自己对应的mountXxx,useState的对应的就是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链表下一个指针|};
   复制代码
 
那么我们去看看他是如何关联起来的: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;}
   复制代码
 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];}
   复制代码
 
根据mountState我们就知道在写useState的时候,解构的语法为什么会解构出想要的东西了。
const [mutation, setMutation] = useState(0) // 纯数字作为初始值
const [mutation, setMutation] = useState(()=>handle(1)) // 函数作为初始值,函数的返回值作为初始值
这里我们遗留了一个问题
针对于上述问题,我们必须去看一下dispatch到底干了什么事情。
 const dispatch: Dispatch<    BasicStateAction<S>,  > = (queue.dispatch = (dispatchAction.bind(    null,    currentlyRenderingFiber,    queue,  ): any));
   复制代码
 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);  }}
   复制代码
 
这里我们可以看到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,  ...};
   复制代码
 
update阶段中的useState调用了updateState,而在updateState中再次调用updateReducer
 function updateState<S>(  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {  //     return updateReducer(basicStateReducer, (initialState: any));}
   复制代码
 
在basicStateReducer中进行了如下处理:
 function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {  // $FlowFixMe: Flow doesn't like mixed types  // 如果更新为函数则是函数执行返回值为更新值  return typeof action === 'function' ? action(state) : action;}
   复制代码
 
这里也就是说明了我们在写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];}
   复制代码
 
所以,useState大致的执行流程如下:
既然useReducer与useState同为状态钩子,那就来看一看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
   复制代码
 
各位可以自行去 codeSandBox 上面去测试玩耍。
分析上述代码我们可以看到每一个div的点击会绑定一个dispatch,这个dispatch可以实现useState里面的setMutation的功能,维护的state值来自于initialState,action为dispatch的入参,针对于这一些,我们去看看源码。
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];}
   复制代码
 
其实这个函数与useState非常类似,只不过他的第一入参为reducer,并且附带了一个可选参数init,官网上面对其称之为惰性初始化。update阶段调用的是updateReducer,在这里不再复述。
生命周期相关的 hook
生命周期相关的这里讲解两个useEffect、useLayoutEffect,面试中经常会问到这两个有什么区别,其实他们并没有什么区别,只是执行的时机不同,具体表现为:
操作 dom 性能相关问题为什么修改 dom 建议放在 useLayoutEffect 中而不是 useEffect 中呢?
mountEffect
 function mountEffect(  // 传入的函数useEffect(()=>{},[])  create: () => (() => void) | void,  // 依赖  deps: Array<mixed> | void | null,): void {  if (__DEV__) {     ...    }  }  return mountEffectImpl(    UpdateEffect | PassiveEffect,    HookPassive,    create,    deps,  );}
   复制代码
 
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,  );}
   复制代码
 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;}
   复制代码
 
这里做的事情,无非就是关联componentUpdateQueue与effect,让React去调度执行。
updateEffect
updateEffect执行调用了updateEffectImpl:
 // updateEffectfunction updateEffect(  // 传入函数  create: () => (() => void) | void,  // 依赖项  deps: Array<mixed> | void | null,): void {  if (__DEV__) {    ...  }  return updateEffectImpl(    UpdateEffect | PassiveEffect,    HookPassive,    create,    deps,  );}
//updateEffectImplfunction 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,  );}
   复制代码
 
本着学习到底的精神,我们一起来看看比较两次的依赖,到底是怎么比较的:
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)   );}
   复制代码
 
上述代码介绍了比较两种值是否一样,其中 Object.is 的浏览器适用版本比较高。做了一次兼容:
上面说了useEffect与useLayoutEffect的结构都是一样的,重点只是在于他们俩的执行时机不一样,那么我们就去深究一下useEffect与useLayoutEffect的执行时机。
生命周期相关的 hook 的执行时机
我们知道所谓生命周期钩子,那肯定是在某一阶段去执行的,这个阶段就是commit阶段。在commit阶段的commitLayoutEffects函数中执行一系列的生命周期钩子,但是对于函数组件来讲,会调度useEffect的create和destroy,也就是执行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);  }}
// enqueuePendingPassiveHookEffectMountexport function enqueuePendingPassiveHookEffectMount(  fiber: Fiber,  effect: HookEffect,): void {  pendingPassiveHookEffectsMount.push(effect, fiber);  if (!rootDoesHavePassiveEffects) {    // root上有没有副作用,默认值为false    rootDoesHavePassiveEffects = true;    scheduleCallback(NormalSchedulerPriority, () => {      flushPassiveEffects();      return null;    });  }}
// enqueuePendingPassiveHookEffectUnmountexport 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;    });  }}
   复制代码
 调度执行 flushPassiveEffects
调度执行flushPassiveEffect会把当前正在执行的任务优先级跟后来新增的优先级进行比较。
 export function flushPassiveEffects(): boolean {    ...    if (decoupleUpdatePriorityFromScheduler) {      // 获得更新优先级      const previousLanePriority = getCurrentUpdateLanePriority();      try {        ...        return runWithPriority(priorityLevel, flushPassiveEffectsImpl);      } finally {        ...      }    } else {      return runWithPriority(priorityLevel, flushPassiveEffectsImpl);    }  }  return false;}
   复制代码
 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;}
   复制代码
 
在这个函数里面就是依次执行上一次的render销毁回调函数、本次的render创建回调函数。而在前面也说过commit流程是无法中断的,只有等所有节点全部commit完,浏览器才会去告知react可以执行自己的调度任务了,也正在此刻useEffect所对应的函数才会去执行,
在生命周期hook里面React帮我们做了一定的性能优化,除了这个还提供了几个手动优化的hook,useMemo和useCallback,那我们来一起瞧瞧吧。
性能优化相关 hook
在讲这一部分之前我们应该搞明白一个问题
先回答第一个问题,为什么要用他们来做性能优化: 我们要知道,React更新流程中只要组件中props或者state发生了变化,那就是必须从变化顶部更新所有的后代组件,牵一发而动全身。有些组件我们并不想要它重新渲染,它却做了一定量的性能牺牲,用useMemo、useCallback就可以改变这样的局面。那么应该怎么用他们来做性能优化呢,主要体现在三个方面:
- 避免无效的副作用 
- 避免无效的累计计算 
- 避免无效的重新渲染 
因为我们前面讲到useEffect,我们可以期望在依赖发生变更的时候去做我们想要做的事情,例如接口请求
 const App = () => {  // 只有当parmas发生变化的时候,我们才回去执行fetch()  useEffect(()=>{    fetch()  }, [params])}
   复制代码
 
这并不是他们俩的功能呢,没关系,我们可以变着法子让他们俩具有类似功能,不过这就有点牵强了。那怎么去做无效的计算和无效的重复渲染呢?有这样的一道面试题:
 // 点击父组件里面的按钮,会不会在子组件里面打印“子组件渲染了”?如果会,该怎么优化?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>;};
   复制代码
 
很明显点击父组件,改变了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(()=>(<Child />),[])}    </div>  );};
// 子组件export const Child = ({}) => {  console.log("子组件渲染了");  return <div>子组件</div>;};
//export const Child = memo(({}) => {//  console.log("子组件渲染了");//  return <div>子组件</div>;//});
   复制代码
 
那么避免无效的计算体现在哪里呢:
 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>  )}
   复制代码
 
上面的代码里面的函数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>  )}
   复制代码
 
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>  )}
   复制代码
 
上述代码中点击父组件的函数,必定会使得count + 1,从而会重新渲染父组件,重新创建函数(不考虑info),对于子组件来说,props改变,就一定会发生重新渲染。针对这种情况,我们一般采用用useCallback包裹起来,然后你去执行点击父组件的函数,你发现他依旧是会重新渲染子组件。因为子组件是函数组件。他没有处理props的浅比较,故而每一次的props他都是不一样的。针对于这种问题解决方式:
因为PureComponent里面会有一个shouldComponentUpdate来处理props的浅比较,而memo则也是我们现在需要去探索的东西。所以对于 useCallback 的使用一定要配合上具有浅比较的组件使用,否则不能优化性能,反而浪费性能。
useMemo适用于大量的计算得出的结果,防止在组建更新的时候,避免不必要的重新计算。
说了这么多,我们还是一起来看看他们是如何实现的吧,根据上面的经验,我们可以在HooksDispatcherOnMount里面找到mountCallback和mountMemo:
mountCallback、mountMemo
 // mountCallbackfunction 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; // 返回传入的函数}
// mountMemofunction 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; // 把传入函数的执行返回值,返回}
   复制代码
 
上述代码简单如斯,mountCallback直接把传入的函数返回出去了,而mountMemo会把传入的函数执行,把返回值返回出去。并没有什么好讲的,我们看看他们怎么更新吧。
updateCallback、updateMemo
 // updateCallbackfunction 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;}
//updateMemofunction 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; // 返回计算结果}
   复制代码
 
两个函数的更新也是异曲同工,细节都写在了代码里面,也没什么好讲的。
useContext
开始看 useContext 之前,我们要明白这个 hook 可以用来干什么。
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
听的云里雾里的?不要担心,说白了就是组件之间传值就完事了,那么你又说了,组件传值我们用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>  )}
   复制代码
 
这样才能在 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>  )}
   复制代码
 
所以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;}
   复制代码
 
上面的代码主要关注于context.Provider和context.Consumer,先来解释一下他们两个的作用吧。
eg:
 // ComponentAconst ComponentA = () => {  const [count, setCount] = useState(0);  return (    <div>      <h4>父级:{count}</h4>      <Content.Provider value={{count,setCount}}>        <ComponentB />      </Content.Provider>    </div>  )}
// ComponentBconst ComponentB = (count) => {  // 需要用到的组件里面用useContext接收  // const [count, setCount] = useContext(Content)  return (    <div>      <context.Consumer>        {(count)=><h4>来自A父级的count:{count}</h4>}      </context.Consumer>    </div>  )}
   复制代码
 
可见不用从useContext中解构出来,而用<context.Consumer>包裹起来,也是可以达到一样的效果的,他们之间的关系大致可以这样表示:
useContext 的执行流程
谈到useContext这里就不得不跟react里面的context一起说一下,在react源码中存在一个valueStack和valueCursor用来记录context的历史信息和当前context,didPerformWorkStackCursor用来表示当前的context有没有变化。
 //ReactFiberNewContext.old.jsconst valueCursor: StackCursor<mixed> = createCursor(null);
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
   复制代码
 
 //ReactFiberStack.old.jsconst valueStack: Array<any> = [];
   复制代码
 
 //pushProviderfunction pushProvider(providerFiber, nextValue) {  var context = providerFiber.type._context;  {    push(valueCursor, context._currentValue, providerFiber);    context._currentValue = nextValue;  }}
//popProviderfunction popProvider(providerFiber) {  var currentValue = valueCursor.current;  pop(valueCursor, providerFiber);  var context = providerFiber.type._context;
  {    context._currentValue = currentValue;  }}
   复制代码
 
因为在render阶段,会以深度优先的方式遍历节点,这样在每一层级都可以通过valueCursor拿到最新的值了。上面也讲了createContext的实现,那么在使用的时候useContext又在干了什么呢?我们通过调用可以看到是调用了readContext,而readContext创建了 dependencies并关联到了fiber的dependencies上面。更新阶段再次调用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;}
   复制代码
 
我们再来看一看render阶段 react 会根据不同的组件类型去执行updateContextProvider,updateContextConsumer。
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;}
   复制代码
 
上面代码可以看到如果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位调度算法  }}
   复制代码
 
计算完 changedBits 之后,如果没有变化就执行bailoutOnAlreadyFinishedWork,跳过当前的节点更新。如果有变化了则执行propagateContextChange去更新当前节点信息。
 // bailoutOnAlreadyFinishedWorkfunction 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;  }}
// propagateContextChangeexport 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;  }}
   复制代码
 updateContextConsumer
updateContextConsumer中,执行prepareToReadContext判断优先级是否足够加入当前这次render,readContext取到当前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;}
   复制代码
 
所以useContext的执行流程大致如下:
属性相关的 hook
对于写原生的朋友来讲,获取一个 dom 节点直接用document.getElementByXxx,是多么的舒服,react也提供了一种获取节点的hook -- useRef 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>    </>  )}
   复制代码
 
在input框中输入文字,点击按钮则可以在控制台获取到输入的文字。对比于原生的,它的优势在于省略掉了那些获取操作。useRef如此简单,还是一起来看一下源码吧。根据以往经验,我们找到mountRef函数。
mountRef
 function mountRef<T>(initialValue: T): {|current: T|} {  const hook = mountWorkInProgressHook();// 获取hook链表
  const ref = {current: initialValue}; // ref是一个对象 <X ref={xxx} />
  if (__DEV__) {    Object.seal(ref);  }  // 存入到hook.memoizedState中  hook.memoizedState = ref;  // 返回 {current: initialValue} 获取通常用x.current  return ref;}
   复制代码
 更新阶段的 useRef
 function updateRef<T>(initialValue: T): {|current: T|} {  const hook = updateWorkInProgressHook();// 获取更新的hook  return hook.memoizedState; // hook返回}
   复制代码
 useRef 的执行流程
我们一猜就知道肯定是在某个阶段对具有ref标记的属性标签,进行了某些处理,肯定是render阶段里面做的,我们去render的beginWork里面去找,果然发现 markRef函数。
markRef
 const ref = workInProgress.ref;  if (    (current === null && ref !== null) ||    (current !== null && current.ref !== ref)  ) {    // Schedule a Ref effect    workInProgress.flags |= Ref;  }}
   复制代码
 
但是在查找的过程中,我们还发现位于completeWork函数中也出现了markRef的身影。
 function markRef(workInProgress: Fiber) {  workInProgress.flags |= Ref;}
   复制代码
 
在这里面做的事情就是给带有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      }    }
    ...}
   复制代码
 
在这个函数里面我们发现了对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;    }  }}
   复制代码
 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;    }  }}
   复制代码
 
所以useRef大致执行流程如下:
总结
本文主要讲解了部分hooks的使用与原理,对hook使用更加熟悉了,还有一部分React内置hook的使用请查看官网,还有基于React的扩展 ahooks 都是值得学习的
评论