第四章: useReducer源码

103 阅读5分钟

前言

useReducer 是 React 中的一个 Hook,用于在函数组件中添加状态。React 会触发组件的重新渲染,从而实现动态更新用户界面的效果。useReducer 是 React 函数组件中实现状态管理的核心工具之一。这个是可以触发React重新渲染的Hook的其中之一。在源码中部分代码和useState共用一套,也有useStateuseReducer的简化版。

源码讲解

关于useReducer部分的源码,实际上分为三个部分,构建阶段分为Mount和Update,以及更改dispatch的调用;

const initialArg = 'cla'
function reducer(state, action) {
  return {
    ...state,
    ...action
  }
}

function createInitialState(name){
  return {
    useName : name
  }
}
const [state, dispatch] = useReducer(reducer, initialArg, createInitialState);

Mount阶段

准备阶段和useState前行一致,useReducer调用的是HooksDispatcherOnMountInDEV.useReducer方法,useReducer函数内部前置会调用一些check方法,和useState的check方法一致,核心代码调用mountReducer(reducer, initialArg, init) 调用mountWorkInProgressHook方法,创建一个workInProgressHook对象,workInProgressHook存在的值有以下几个:

const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
}

第一个useReducer会赋值给fiber.memoizedState,如果是非第一个useReducer,将workInProgressHook对象放在上一个workInProgressHook.next;
第三个参数是可选参数,例子中createInitialState,是可选函数,调用createInitialState(initialArg) 的结果作为初始值,否则initialArg 作为初始值,将初始值赋值给hook.memoizedState = hook.baseState = initialState
创建一个queue对象:

const queue = {
    pending: null,
    interleaved: null,
    lanes: 0,
    dispatch:  dispatchReducerAction.bind(null, fiber, queue),
    lastRenderedReducer: reducer,
    lastRenderedState: initialState
  };

赋值workInProgressHook.queue = queue HooksDispatcherOnMountInDEV.useReducerreturn值[workInProgressHook.memoizedState,queue.dispatch]
以上就是Mount阶段所有的代码,以下总结:

  • 往fiber.memoizedState上挂载数据;
  • 最多三个参数,第三个可选函数参数,调用arguments[2](arguments[1])否则arguments[1]作为初始状态;
  • 返回长度为2的数组,第一个为状态,第二个为方法;
  • useState相似,都是放在memoizedState上,创建hook和queue对象;

调用dispatch

dispath(action);

创建更新任务队列:

const update = {
    lane: 0, // //这个涉及到lane模型,先忽略这个值的意义
    action: action,
    hasEagerState: false,
    eagerState: null,
    next: null
  }

update是一个环状链表,通过next指向下一个,将update更新链表放在queue.pending 更新队列下,这样多个dispacth的时候,pending会指向最后一个update更新对象,这个的好处是,每次添加的时候不需要从头遍历,直接在当前对象后添加即可,更新遍历的时候,只需要指向next就可以找到头部,可以做到很好的性能优化;
update是一个环状链表,将queue.pedding = update
以下流程图指向:

graph TD; 
A[update1]--> B[update2]; B --> C[update3]; C --> A;

dispach只会将状态存储起来,之后在update阶段才会处理状态,得到最终状态的结果;
这里会调用组件更新的方法,这个一定会组件更新的,和setState有些不同,setState调用可能不会更新,useReducer一定会更新;
以下总结:

  • 所有dispatch操作,都会创建update对象,update是个链表,放在queue.pending存储下来;
  • queue.pending指向最后一个dispacth创建的update对象;
  • dispacth只有做保存操作,没有任何状态更新处理;
  • 调用dispacth方法一定会触发组件更新;

update阶段

更新阶段调用的是HooksDispatcherOnUpdateInDEV.useReducer方法,开始调用一些check方法准备阶段,核心调用updateReducer(reducer, initialArg, init), copy current fiber.memoizedState 上的属性,赋值给当前fiber上,
更新reducer方法,可以使mount阶段update阶段的reducer方法不是同一个,甚至在不同的update中都调用不同的reducer方法,mount阶段的reducer只是存储,没有任何作用,后面调用的都是update阶段传入的参数;
找到第一个update更新对象,调用最新的reducer方法reducer(newState, action), newState是dispach之前最新的state,初始值是hook.baseState,两个dispacth的newState可能不同。
更新数据hook.memoizedState = hook.baseState = queue.lastRenderedState = newState 返回 [hook.memoizedState,queue.dispatch]

以下总结:

  • 先清空workInProgress fiber 上的 memoizedState,然后将current fiber的memoizedState浅拷贝给workInProgress fiber的 memoizedState上;
  • 拿到queue.pending.next ,作为第一个更新update;
  • 调用reducer方法都是拿实时最新的,可以在更新的任何阶段替换reducer方法;
  • 遍历所有的update对象,将最后一个值更新到memoizedState等属性上,
  • 清空queue.pending;
  • 返回最新的memoizedState和dispatch方法;

数据存储结构参考useState

useState 和 useReducer异同

相同点:
  • 都是可以更改状态,触发组件重新渲染的;
  • 都是返回数组,数组第一个为数据,第二项为更改数据的方法;
  • 都是异步更新,触发更新方法,合并之后统一更新;
  • 在使用上,可以把useState看成是useReducer的简化,react底层帮助useState实现了一个简化的reducer方法;
不同点:
  • 用法不同,useState只接受一个参数,useReducer可以接收三个参数;
  • usetate有eagerState的机制,即使触发更新方法也可能不会更新,useReducer只要触发更新方法,就一定的会导致重新渲染;
  • useState处理一些简单数据比较方便,useReducer处理一些复杂场景更便利;
  • 在底层代码中useState会比useReucer处理的更繁琐一点;

补充额外知识点

useStateuseReducer有一些代码都是使用同一部分,所有hook,queue,update的结构都差不多,更新遍历的方法都是一样的,甚至有一些情况会使用useStateuseReducer搭配使用更新组件状态;
是否能放在条件语句中使用参考useState,原理是一样的;
在更新中触发更新也是和useState一致的;