前言
useReducer
是 React 中的一个 Hook,用于在函数组件中添加状态。React 会触发组件的重新渲染,从而实现动态更新用户界面的效果。useReducer
是 React 函数组件中实现状态管理的核心工具之一。这个是可以触发React重新渲染的Hook的其中之一。在源码中部分代码和useState
共用一套,也有useState
是useReducer
的简化版。
源码讲解
关于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
以下流程图指向:
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处理的更繁琐一点;
补充额外知识点
useState 和useReducer 有一些代码都是使用同一部分,所有hook,queue,update的结构都差不多,更新遍历的方法都是一样的,甚至有一些情况会使用useState 和useReducer 搭配使用更新组件状态;
是否能放在条件语句中使用参考useState,原理是一样的;
在更新中触发更新也是和useState一致的;