从react源码看hooks的原理

React暴露出来的部分Hooks

//packages/react/src/React.js
export {
  ...
  useCallback,
  useContext,
  useEffect,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
  ...
}

功能描述

  • 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),
};

其实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的一种,他也有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:
      ...
    }
}

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;
  }
}

  • 如果是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,
  ...
};

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链表下一个指针
|};

那么我们去看看他是如何关联起来的: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执行的时候会触发渲染更新,以及二次更新,那么他是怎么实现的呢?

针对于上述问题,我们必须去看一下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大致的执行流程如下:

在这里插入图片描述

既然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

各位可以自行去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];
}

其实这个函数与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,
  );
}

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;
}

这里做的事情,无非就是关联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,
  );
}

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

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的浏览器适用版本比较高。做了一次兼容:

上面说了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;
    });
  }
}

调度执行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帮我们做了一定的性能优化,除了这个还提供了几个手动优化hookuseMemouseCallback,那我们来一起瞧瞧吧。

性能优化相关hook

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

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

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

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

因为我们前面讲到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他都是不一样的。针对于这种问题解决方式:

  • 子组件写成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; // 把传入函数的执行返回值,返回
}

上述代码简单如斯,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; // 返回计算结果
}

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

useContext

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

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>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>
  )
}

这样才能在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.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>
  )
}

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

在这里插入图片描述

useContext的执行流程

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

//ReactFiberNewContext.old.js
const valueCursor: StackCursor<mixed> = createCursor(null);

const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);

//ReactFiberStack.old.js
const valueStack: Array<any> = [];

  • 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;
  }
}

因为在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;
}

我们再来看一看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;
}

上面代码可以看到如果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去更新当前节点信息。

// 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;
  }
}

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;
}

所以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>
    </>
  )
}

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阶段里面做的,我们去renderbeginWork里面去找,果然发现 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的处理:

  • 如果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;
    }
  }
}

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都是值得学习的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React Hooks 是 React 16.8 版本引入的一种新特性,它是为了使函数组件能够拥有状态和其他 React 特性而设计的。 React Hooks 的原理基于两个核心概念:闭包和钩子函数。 1. 闭包:在函数组件内部,可以通过闭包的方式引用外部作用域的变量。React Hooks 利用了闭包的特性,使得可以在函数组件内部存储和更新状态。 2. 钩子函数:React Hooks 提供了一系列的钩子函数,如 useState、useEffect、useContext 等。这些钩子函数是预定义的特殊函数,可以在函数组件中使用,通过调用这些钩子函数,可以获取和操作组件的状态、副作用和上下文等。 当一个组件使用了 React Hooks,React 在底层会创建一个与该组件实例相关联的 Fiber 对象,并在组件渲染时执行组件函数。在执行组件函数时,React 跟踪每个组件函数内部所有的钩子函数调用,并将其与该组件实例相关联。 当组件函数执行时,如果遇到 useState 钩子调用,React 会查找该钩子函数对应的状态值,并将其返回给组件函数。组件通过 useState 返回的状态值可以读取和更新组件的状态。 当组件函数执行完毕后,React 会将该组件的状态和副作用存储在 Fiber 对象中,并将 Fiber 对象添加到更新队列中。之后,React 会根据更新队列中的 Fiber 对象,对组件进行批量更新,实现页面的重新渲染。 通过这种方式,React Hooks 实现了函数组件的状态管理和副作用处理,使得开发者可以更方便地编写和维护 React 组件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值