useReducer 源码中的链表实现

211 阅读2分钟

useReducer返回state和dispatch,两个值,一个函数组件可以调用多个useReducer,不同的useReducer的state按顺序以链表的形式保存在该函数组件的fiber,memorizeState属性里。首次渲染会生成链表,更新时会从旧的fiber里按顺序拿到链表中对应的自己的值。

下面,让我们一步步来实现一下useReducer

1.先声明一个基本的架构

hook的数据结构是这样的
   hook = {
      memorizeState: null,
      next: null
    }
function updateWorkProgressHook() {}

export function useReducer(reducer, initialState) {
  // 声明函数,获取当前fiber的hook
  const hook = updateWorkProgressHook()

  if (!currentlyRenderingFiber.alternate) {
    // 通过是否有旧的fiber来判断是否是初次渲染
    // 初次渲染hook的memorizestate 设置为初始值
    hook.memorizeState = initialState
  }
  const dispatch = (action) => {

    hook.memorizeState = reducer(hook.memorizeState, action)
    // 更新该组件,更新组件的方法从外部引入,这里先不讨论
    // 参数为当前正在渲染中的fiber
    scheduleUpdateOnFiber(currentlyRenderingFiber)
  }
  return [hook.memorizeState, dispatch]
}

当dispatch执行时,先会改变该useReducer的hook的state,然后触发组件重新协调渲染。组件再次执行时改useReducer返回的state就是dispatch更新后的state值。

2.如何在useReducer执行前拿到当前函数组件的fiber对象

// 拿到当前节点
let currentlyRenderingFiber = null
// 拿到链表中当前hook
let workInProgressHook = null

// 该函数会在函数组件执行前触发,使函数组件执行时里面的useReducer能拿到currentlyRenderingFiber
export function renderHooks(wip) {
  // 保存当前fiber
  currentlyRenderingFiber = wip
  //函数组件执行前改函数组件的fiber中的memorizedState值为null
  currentlyRenderingFiber.memorizedState = null;
  //函数组件执行前hook链表的当前节点为null
  workInProgressHook = null
}

3.如何获取当前fiber的hook

// 首次渲染会生成新链表,更新时会从旧的fiber,memorizeState里按顺序拿到链表中对应的自己的值。
function updateWorkProgressHook() {
  const current = currentlyRenderingFiber.alternate
  let hook;
  // 判断是否是新节点
  if (current) {
    // 有老节点,获得老节点对应的hook,返回hook
    currentlyRenderingFiber.memorizeState = current.memorizeState

    if (workInProgressHook) {
      //是否头节点
      hook = workInProgressHook = workInProgressHook.next
    } else {
      hook = workInProgressHook = current.memorizeState
    }
  } else {
    hook = {
      memorizeState: null,
      next: null
    }
    // 新节点:建立hook链表
    if (workInProgressHook) {
      // 是否头节点
      workInProgressHook = workInProgressHook.next = hook
    } else {
      workInProgressHook = currentlyRenderingFiber.memorizeState = hook
    }
  }
  return hook
}