来看看 vue3.0 跟 React16 + HOOK的源码对比 哪个香?(1)

4,783 阅读4分钟

 React:

 react hook

React.js

import {
  useContext,
  useEffect,
  useState,
} from './ReactHooks'

我们看看这三个函数具体源码实现

export function useState<S>(initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

export function useEffect(
  create: () => mixed,
  inputs: Array<mixed> | void | null,
) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, inputs);
}

export function useContext<T>(
  Context: ReactContext<T>,
  observedBits: number | boolean | void,
) {
  const dispatcher = resolveDispatcher();
  // dev code
  return dispatcher.useContext(Context, observedBits);
}
HooksReact中的源码,可见他们也跟其他React的API一样,只管定义,不管实现。他们都调用了ReactCurrentOwner.currentDispatcher.xxx对应的方法。那么这个ReactCurrentOwner.currentDispatcher是啥呢?

在我们执行renderRoot开始渲染的时候,我们会设置这个值:

import {Dispatcher} from './ReactFiberDispatcher';

if (enableHooks) {
  ReactCurrentOwner.currentDispatcher = Dispatcher;
}

并且在离开renderRoot的时候设置为null。那么这个Dispatcher是什么呢?

// ReactFiberDispatcher.js
import {readContext} from './ReactFiberNewContext';
import {
  useCallback,
  useContext,
  useEffect,
  useImperativeMethods,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
} from './ReactFiberHooks';

export const Dispatcher = {
  readContext,
  useCallback,
  useContext,
  useEffect,
  useImperativeMethods,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
};

OK,Hooks方法的源头代码找到了。需要注意的是Hooks只有FunctionalComponent被更新的时候才会被调用,所以我们肯定需要关心一下FunctionalComponent的更新过程。

Hooks方法的源头代码:需要注意的是Hooks只有FunctionalComponent被更新的时候才会被调用,所以我们肯定需要关心一下FunctionalComponent的更新过程。

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  prepareToUseHooks(current, workInProgress, renderExpirationTime);
  nextChildren = Component(nextProps, context);
  nextChildren = finishHooks(Component, nextProps, nextChildren, context);

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

可以看到这里增加了三个方法调用:prepareToReadContextprepareToUseHooksfinishHooks。后两个都明显跟Hooks有关,而第一个是读取新的Context API的,因为在Hooks中有读取Context的操作,所以增加这个就无可厚非了。我们看一下prepareToUseHooksfinishHooks分别做了什么。

prepareToUseHooks

这个方法来自ReactFiberHooks.js,明显只是初始化一些模块内的全局变量。

export function prepareToUseHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  nextRenderExpirationTime: ExpirationTime,
): void {
  renderExpirationTime = nextRenderExpirationTime;
  currentlyRenderingFiber = workInProgress;
  firstCurrentHook = current !== null ? current.memoizedState : null;
}


finishHooks

相比之下finishHooks则复杂很多,一进来就有一个while循环,这个循环是因为Hooks提供了我们一个功能:如果在一次更新中(也就是调用FunctionalComponent的过程中)如果直接调用了类似setState的Hooks API产生了新的更新,则会在当前的渲染周期中直接执行更新。这个大家可以先了解一下,后面我们具体讲Hooks的实现会再具体讲到。

后面设置了renderedWork.updateQueue,就非常类似于HostComponentClassComponent了,本来FunctionalComponentcommit阶段是完全没有更新的,但是现在Hooks给了他产生side effect的能力,所以这就是记录这些side effectqueue

后面就是初始化全局变量了,就先不多讲了。


export function finishHooks(
  Component: any,
  props: any,
  children: any,
  refOrContext: any,
): any {
  while (didScheduleRenderPhaseUpdate) {
    didScheduleRenderPhaseUpdate = false;
    numberOfReRenders += 1;

    // Start over from the beginning of the list
    currentHook = null;
    workInProgressHook = null;
    componentUpdateQueue = null;

    children = Component(props, refOrContext);
  }
  renderPhaseUpdates = null;
  numberOfReRenders = 0;

  const renderedWork: Fiber = (currentlyRenderingFiber: any);

  renderedWork.memoizedState = firstWorkInProgressHook;
  renderedWork.expirationTime = remainingExpirationTime;
  renderedWork.updateQueue = (componentUpdateQueue: any);

  const didRenderTooFewHooks =
    currentHook !== null && currentHook.next !== null;

  renderExpirationTime = NoWork;
  currentlyRenderingFiber = null;

  firstCurrentHook = null;
  currentHook = null;
  firstWorkInProgressHook = null;
  workInProgressHook = null;

  remainingExpirationTime = NoWork;
  componentUpdateQueue = null;

  return children;
}

useState

当我们的代码执行到了

useState

的时候,他到底做了什么呢?

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return useReducer(
    basicStateReducer,
    // useReducer has a special case to support lazy useState initializers
    (initialState: any),
  );
}

可见

useState

不过就是个语法糖,本质其实就是

useReducer

具体用来做什么呢?

最开始的两句代码是每个Hooks都会做的统一代码:

currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();

这里分两种情况:第一次渲染和更新,如果存在workInProgressHook.queue则为更新,否则是第一次渲染

第一次渲染

第一次渲染主要就是初始化操作

// There's no existing queue, so this is the initial render.
if (reducer === basicStateReducer) {
  // Special case for `useState`.
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
} else if (initialAction !== undefined && initialAction !== null) {
  initialState = reducer(initialState, initialAction);
}
workInProgressHook.memoizedState = workInProgressHook.baseState = initialState;

这里初始化 initialState,并且记录在workInProgressHook.memoizedStateworkInProgressHook.baseState上然后创建queue对象

queue = workInProgressHook.queue = {
  last: null,
  dispatch: null,
};

这一看到queue的结构非常简单,只有一个last指针和dispatchdispatch是用来记录更新

state的方法的,接下去我们就要创建dispatch方法了

const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
  null,
  currentlyRenderingFiber,
  queue,
): any));

可以看到这个dispatch就是dispatchAction绑定了对应的Fiberqueue。最后returnreturn [workInProgressHook.memoizedState, dispatch];对应了我们const [state, updateState] = useState('default')的用法

更新

分两种情况,是否是reRender,所谓reRender就是说在当前更新周期中又产生了新的更新,就继续执行这些更新知道当前渲染周期中没有更新为止他们基本的操作是一致的,就是根据reducer和update.action来创建新的state,并赋值给Hook.memoizedState以及Hook.baseState。注意这里,非对于reRender得情况,我们会对每个更新判断其优先级,如果不是当前整体更新优先级内得更新会跳过,一个第得展示跳过Update会变成新的baseUpdate,他记录了在之后所有得Update,即使是优先级比他高得,因为在他被执行得时候,需要保证后续的更新要在他更新之后的基础上再次执行,因为结果可能会不一样。

 太累了 歇歇 喘口气