从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 =
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值