第四章: useReducer源码

前言

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

源码讲解

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

js 复制代码
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存在的值有以下几个:

js 复制代码
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对象:

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

赋值workInProgressHook.queue = queue HooksDispatcherOnMountInDEV.useReducer return值[workInProgressHook.memoizedState,queue.dispatch]

以上就是Mount阶段所有的代码,以下总结:

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

调用dispatch

js 复制代码
dispath(action);

创建更新任务队列:

js 复制代码
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一致的;

相关推荐
cs_dn_Jie29 分钟前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic1 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿1 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具2 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161772 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test3 小时前
js下载excel示例demo
前端·javascript·excel
Yaml43 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事3 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶3 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo3 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx