likes
comments
collection
share

useReducer源码

作者站长头像
站长
· 阅读数 11

useReducer

  • hooks
  • 如何使用
function counter(state, action) {
  if (action.type === "add") return state + action.payload;
  return state;
}
const App = () => {
  const [number, setNumber] = React.useReducer(counter, 0);

  <button
    {...attrs}
    onClick={() => {
      setNumber({ type: "add", payload: 1 }); //update1=>update2=>update3=>update1
      setNumber({ type: "add", payload: 2 }); //update2
      setNumber({ type: "add", payload: 3 }); //update3
    }}
  >
    {number}
  </button>;
};

实现原理

  • hooks只存在于函数组件中,而函数组件的初始化阶段,我们需要执行它,然后拿到子节点
  • renderWithHooks用来执行函数组件,并返回子节点
    • 而且内部会做新老hook的处理
const HooksDispatcherOnMount = {
  useReducer: mountReducer
}
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, currentlyRenderingFiber, queue));
  return [hook.memoizedState, dispatch];
}
function mountWorkInProgressHook() {
  const hook = {
    memoizedState: null,//hook的状态 0
    queue: null,//存放本hook的更新队列 queue.pending=update的循环链表
    next: null //指向下一个hook,一个函数里可以会有多个hook,它们会组成一个单向链表
  };
  if (workInProgressHook === null) {
    //当前函数对应的fiber的状态等于第一个hook对象
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}
/**
 * 执行派发动作的方法,它要更新状态,并且让界面重新更新
 * @param {*} fiber function对应的fiber
 * @param {*} queue hook对应的更新队列
 * @param {*} action 派发的动作
 */
function dispatchReducerAction(fiber, queue, action) {
  //在每个hook里会存放一个更新队列,更新队列是一个更新对象的循环链表update1.next=update2.next=update1
  const update = {
    action,//{ type: 'add', payload: 1 } 派发的动作
    next: null//指向下一个更新对象
  }
  //把当前的最新的更添的添加更新队列中,并且返回当前的根fiber
  const root = enqueueConcurrentHookUpdate(fiber, queue, update);
  scheduleUpdateOnFiber(root);
}
/**
 * 渲染函数组件
 * @param {*} current 老fiber
 * @param {*} workInProgress 新fiber
 * @param {*} Component 组件定义
 * @param {*} props 组件属性
 * @returns 虚拟DOM或者说React元素
 */
export function renderWithHooks(current, workInProgress, Component, props) {
  currentlyRenderingFiber = workInProgress;//Function组件对应的fiber
  //如果有老的fiber,并且有老的hook链表
  if (current !== null && current.memoizedState !== null) {
    // 暂时忽略
    ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
  } else {
    // ☆首次挂载
    ReactCurrentDispatcher.current = HooksDispatcherOnMount;
  }
  //需要要函数组件执行前给ReactCurrentDispatcher.current赋值
  const children = Component(props);
  currentlyRenderingFiber = null;
  workInProgressHook = null;
  currentHook = null;
  return children;
}
  • 可以看到ReactCurrentDispatcher.current里存了一个object,里面的useReducer为一个函数
  • 它是React中一个内部变量的引用,它跟useReducer有直接的关联
  • 现在就到了执行的时候了,我们需要去看React.useReducer的声明
//ReactCurrentDispatcher.js-----------

const ReactCurrentDispatcher = {
  current: null
}
export default ReactCurrentDispatcher;

// react----------------
import ReactCurrentDispatcher from './ReactCurrentDispatcher';

function resolveDispatcher() {
  return ReactCurrentDispatcher.current;
}

/**
 * 
 * @param {*} reducer 处理函数,用于根据老状态和动作计算新状态
 * @param {*} initialArg 初始状态
 */
export function useReducer(reducer, initialArg) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg);
}
  • 现在就可以看出作用了,React.useReducer === ReactCurrentDispatcher.current.useReducer

hooks初始阶段

  • 执行mountWorkInProgressHook
  • 函数组件fiber的memoizedState始终指向第一个hooks实例
  • hook实例本身也是一个链表
  const hook = {
    memoizedState: null, // 当前值
    queue: {
        pending: null, // 更新队列
        dispatch: null // 更新函数
    },
    next: null // 下一个hook实例
  };
  • 然后给hook.memoizedState赋值,给hook.queue.dispatch赋值
  • dispatch的作用是入队列和触发更新
  • 返回[hook.memoizedState, hook.queue.dispatch]
  • 可以看到,在首次初始化的时候,传入的reducer并未使用
  • 如果是多个hook,就是next向下连接
  • hook初始化完成了,函数就可以拿到值了,然后执行函数,函数执行完毕,清除闭包里的currentlyRenderingFiber和workInProgressHook,因为是公用的,防止被污染

更新

  • 更新触发了上面的dispatch,进行了入队处理
  • 这里存储的不是链表了,而是数组,每次向数组内加3项,分别为fiber、queue、update
    • update里的action就是触发dispatch的时候传进来的options
    • 这个数组是一个公共变量
  • 然后向上查找的根fiber,并传递给重新渲染函数

视图刷新

  • ensureRootIsScheduled
  • 这里加了一层处理,因为我们的有时候一次会传入多个更新,那么都扔在一个queue里了,如果不处理会多次更新,没有必要,如果有更新的就退出本轮
  • 也可以理解为更新合并
function ensureRootIsScheduled(root) {
  // 相当于防抖吧,减少无意义更新次数
  if (workInProgressRoot) return;
  workInProgressRoot = root;
  //告诉 浏览器要执行performConcurrentWorkOnRoot 在此触发更新
  scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
  • 在视图刷新前执行finishQueueingConcurrentUpdates
  • queue里的待更新统一放到hook自身的队列中,和初始化element的时候一样
    • 最后一个更新为pending,它指向第一个更新,单向循环链表
  • 然后依然是处理另外的根fiber,然后开始处理节点
  • 函数组件依然会走renderWithHooks,但这次就是更新逻辑了

renderWithHooks的更新

const HooksDispatcherOnUpdate = {
  useReducer: updateReducer,
};

function updateWorkInProgressHook() {
  //获取将要构建的新的hook的老hook
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    currentHook = current.memoizedState;
  } else {
    currentHook = currentHook.next;
  }
  //根据老hook创建新hook
  const newHook = {
    memoizedState: currentHook.memoizedState,
    queue: currentHook.queue,
    next: null,
  };
  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
  } else {
    workInProgressHook = workInProgressHook.next = newHook;
  }
  return workInProgressHook;
}
function updateReducer(reducer) {
  //获取新的hook
  const hook = updateWorkInProgressHook();
  //获取新的hook的更新队列
  const queue = hook.queue;
  //获取老的hook
  const current = currentHook;
  //获取将要生效的更新队列
  const pendingQueue = queue.pending;
  //初始化一个新的状态,取值为当前的状态
  let newState = current.memoizedState;
  if (pendingQueue !== null) {
    queue.pending = null;
    const firstUpdate = pendingQueue.next;
    let update = firstUpdate;
    do {
      const action = update.action;
      newState = reducer(newState, action);
      update = update.next;
    } while (update !== null && update !== firstUpdate);
  }
  hook.memoizedState = newState;
  return [hook.memoizedState, queue.dispatch];
}
  • 依次拿到旧fiber的memoizedState,里面存的是hooks队列,依次执行刚才存储的更新,通过出入的reducer函数处理后,赋值给hooks实例的memoized
  • 最终返回新的[hook.memoizedState,初始的dispatch]
  • 然后进行dom的构建