useReducer - mount

65 阅读2分钟

useReducer 是一个函数。接收两个参数。reducer 函数和 state 初始状态。返回状态和回掉函数。

function reducer(state, action) {
  if (type === "add") {
    return state + 1;
  }
  return state;
}
function FunctionComponent() {
  const [number, setNumber] = React.useReducer(reducer, 0);
  return (
    <div>
      <button
        onClick={() => {
          setNumber({ type: "add" });
        }}
      >
        +
      </button>
      <span>{number}</span>
    </div>
  );
}

实现

在 react 中,实现 useReducer 时做了很多变量转发,但是在挂载阶段,实际上就是执行了 mountReducer 函数。这个函数主要功能是对 hook 进行初始化,并返回初始状态和回掉函数。其中调用了 mountWorkInProgressHook 函数,主要作用是构建一个单向 hook 链表,并将当前 fiber 的 memoizedState 字段指向第一个 hook。

function mountReducer(reducer, initialArg) {
  const hook = mountWorkInProgressHook();
  hook.memoizedState = initialArg;
  const queue = {
    pending: null,
    dispatch: null,
  };
  hook.queue = queue;
  const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentRenderingFiber, queue));
  return [hook.memoizedState, dispatch];
}

/**
 * 挂载构建中的 Hook
 * @returns
 */
function mountWorkInProgressHook() {
  const hook = {
    memoizedState: null, // 缓存的 state, hook 状态
    queue: null, // 存放本 hook 的更新队列
    next: null, // 指向下一个 hook 单向链表
  };

  if (workInProgressHook === null) {
    // 当前函数对应的 fiber 的状态等于第一个 hook
    currentRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    workInProgressHook = workInProgressHook.next = hook;
  }

  return workInProgressHook;
}

这时,当初次挂载完成,用户点击按钮,调用 setNumber 时,执行的就是 dispatch 函数,也就是 dispatchReducerAction 函数。这个函数的作用是处理当前 hook 的更新队列,也就是初始化更新对象并将更新对象绑定到对应的更新队列。这里的绑定是通过数组实现的,数组中每三个元素为一组,分别为 fiber,更新队列,更新对象。最后调用 scheduleUpdateOnFiber 函数,渲染数据。

/**
 * 执行派发动作的方法,他要更新状态,并且让页面重新更新
 * @param {*} fiber function 对应的 fiber
 * @param {*} queue hook 的更新队列
 * @param {*} action 派发的动作
 */
function dispatchReducerAction(fiber, queue, action) {
  // 每个 Hook 对应一个更新链表
  const update = {
    action,
    next: null,
  };

  // 把当前最新的更新添加到更新队列中,并且返回当前的根 fiber
  const root = enqueueConcurrentHookUpdate(fiber, queue, update);
  scheduleUpdateOnFiber(root);
}

/**
 * 把更新对象添加到更新队列中
 * @param {*} fiber 函数组件对应 fiber
 * @param {*} queue 要更新 hook 更新队列
 * @param {*} update 更新对象
 */
export function enqueueConcurrentHookUpdate(fiber, queue, update) {
  enqueueUpdate(fiber, queue, update);
  return getRootForUpdatedFiber(fiber);
}

function getRootForUpdatedFiber(sourceFiber) {
  let node = sourceFiber;
  let parent = node.return;
  while (parent !== null) {
    node = parent;
    parent = node.return;
  }
  return node.tag === HostRoot ? node.stateNode : null;
}

function enqueueUpdate(fiber, queue, update) {
  concurrentQueue[concurrentQueueIndex++] = fiber;
  concurrentQueue[concurrentQueueIndex++] = queue;
  concurrentQueue[concurrentQueueIndex++] = update;
}