Skip to content

sourceCode

overall

React 要做的事情,其实就是数据驱动分块渲染。如果单想做分块渲染很简单,在初次渲染时,可以知道每个 dom 节点和对应的数据/函数组件,数据变动时,重新调用函数组件生成新的 vdom 节点,再重新生成对应的 dom 节点。难点在于数据变动生成新节点的过程需要打碎异步进行,这就面临着调度及其优化。

大致流程图

第一次执行:

react

update:

React-update

lane

Just like lanes on the road, the rule is to drive on different lanes based on your speed, meaning the smaller the lane is the more urgent the work is, the higher priority it is. so SyncLane is 1 here.

bitwise operation

export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
  return (set & subset) === subset;
}

export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a | b;
}

requestUpdateLane(fiber):lane

priority 相关

React 为不同的操作设置了不同的优先级,比如点击、input输入为同步优先级

避免饥饿:设置过期时间;高优先级任务插队

anything

:hatched_chick: React.createElement // used by @babel/plugin-transform-react-jsx

:hatched_chick: React.createRef:

// an immutable object with a single mutable value
export function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  if (__DEV__) {
    Object.seal(refObject);
  }
  return refObject;
}

Fiber:

// Dependencies (contexts, events) for this fiber, if it has any

fiber.dependencies: Dependencies | null

形式上是链表,这样可以记住暂停的位置;像之前的递归算法就得一口气算到头

hooks

useHooks only called when function firstly called

mountWorkInProgressHook(): 构造 workInProgressHook, 以链表形式保存 hook

updateWorkInProgressHook(): 返回 hook,若是链表头,则从 currentlyRenderingFiber.memoizedState 中读取 hook 复制

hooks can only called within the function

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  invariant(
    dispatcher !== null,
    'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
      ' one of the following reasons:\n' +
      '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
      '2. You might be breaking the Rules of Hooks\n' +
      '3. You might have more than one copy of React in the same app\n' +
      'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
  );
  return dispatcher;
}

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

//there are some more dispatchers for other state 
export const ContextOnlyDispatcher: Dispatcher = {
  readContext,

  useCallback: throwInvalidHookError,
  useContext: throwInvalidHookError,
  useEffect: throwInvalidHookError,
  useImperativeHandle: throwInvalidHookError,
  useLayoutEffect: throwInvalidHookError,
  useMemo: throwInvalidHookError,
  useReducer: throwInvalidHookError,
  useRef: throwInvalidHookError,
  useState: throwInvalidHookError,
  useDebugValue: throwInvalidHookError,
  useDeferredValue: throwInvalidHookError,
  useTransition: throwInvalidHookError,
  useMutableSource: throwInvalidHookError,
  useOpaqueIdentifier: throwInvalidHookError,

  unstable_isNewReconciler: enableNewReconciler,
};
// In renderWithHooks(), after Component() call executed
ReactCurrentDispatcher.current = ContextOnlyDispatcher;

other middle variables

ReactCurrentOwner.current = workInProgress;

if (__DEV__) {
    if (current !== null && current.memoizedState !== null) {
      ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
    } else if (hookTypesDev !== null) {
      // This dispatcher handles an edge case where a component is updating,
      // but no stateful hooks have been used.
      // We want to match the production code behavior (which will use HooksDispatcherOnMount),
      // but with the extra DEV validation to ensure hooks ordering hasn't changed.
      // This dispatcher does that.
      ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
    } else {
      ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
    }
  } else {
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }

hooks cant be nested

 useState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      currentHookNameInDev = 'useState';
      mountHookTypesDev();
      const prevDispatcher = ReactCurrentDispatcher.current;
      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
      try {
        return mountState(initialState);
      } finally {
        ReactCurrentDispatcher.current = prevDispatcher;
      }
    },

useState

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
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    // every hooks would be listed 链表
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    // $FlowFixMe: Flow doesn't like mixed types
    initialState = initialState();
  }
  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, // 作用在存储多次 dispatch
  ): any)); // 初始化时的 setXXX 就是 function dispatchAction, 这里绑定了 fiber
  return [hook.memoizedState, dispatch];
}

通过 dispatchAction 存入新值时,如何触发 update 或组件函数的重新渲染呢?

dispatchAction() ->

  • const lane = requestUpdateLane(fiber); // 根据 fiber 的 mode, 选择不同优先级的 lane 返回

  • queue.pending = update // update.lane update.action(要触发的动作) update.next(回环链表)

  • scheduleUpdateOnFiber(fiber, lane, eventTime) ->
  • performSyncWorkOnRoot(root) or schedulePendingInteractions(root, lane);

useEffect

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

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

那什么时候执行 effects 里的回调呢?flushPassiveEffects(), 主要在 commit phase 的 commitRoot() 里