React源码系列(五)------ useReducer
前言
本文是该系列第五篇,经过了上一篇的事件系统和本文的状态定义和修改,我们正式进入到React的更新流程。
useReducer
在React中几乎所有的hooks都有着两种形态,一种是mount,在初次挂载时调用,一种是update,在更新时调用。
对此就有疑惑了,我们日常使用都是usexxx,根本没有mountxxx或者updatexxx啊?
这是React内部会做的事,可以想象成我们平时都从一个池子里取hooks来用,而这个池子的内容有两套,一套全是mountHooks,一套全是updateHooks,当我们开始构建函数组件的fiber时,会根据当前函数组件的fiber有没有替身来判断hooks这个池子的内容究竟是mountHooks(无替身)还是updateHooks(有替身)。
ps:替身的概念在beginWork的双缓冲原理中。
hook上几个重要的属性
hook身上有着很多属性,现在我们先来看看几个基本的属性。
hook = {
// hook的状态,也就是我们使用的值,如useState里的state
memoizedState: null,
// 存在本hook的更新队列
queue: {
// 最新的更新
pengding: null,
// 上一个reducer
lastRenderedReducer: null,
// 上一个状态state
lastRenderedState: null,
// 可以用setState来理解这个属性
dispatch: null,
},
// 指向下一个hook,一个函数组件可能存在多个hook,这些hook会组成一个单向链表
next: null,
};
这里说一下hook.queue。这个属性存的是一个单向循环链表,我们多次调用一个hook的setState的时候就会形成以下这样的一个链表。
onClick={() => {
dispatch({ type: 'add', payload: 1 });
dispatch({ type: 'add', payload: 2 });
dispatch({ type: 'add', payload: 3 });
}}
这里之所以要形成这样一个链表,是为了批量更新(批量更新相关内容后续在lane优先级中讲解)做准备,我们知道在同一个执行上下文多次调用setState他只会执行一次调度,而这次调度能计算出多个setState后的准确的值就要依赖这个链表。
hooks调用原理图
根据上面的信息,我们可以整理出一个这样的大致的原理图。
图中为一个函数组件的fiber的hooks链表,每一次更新时,hooks链表上的每一个hook都会根据自身的queue计算出最新的newState,然后存储到自身memoizedState上(图中只画了hook1的计算流程,而hook2,hook3也会如hook1一样走一遍这个流程)。
useReducer主流程图
在beginWork中讲过,fiber上有一个属性叫tag,他记录着当前fiber的类型,如果是函数组件,他就会走函数组件fiber构建的函数,其中会执行一个方法叫renderWithHooks,这个方法就是核心。
/**
* 根据新虚拟DOM构建新的fiber子链表
* @date 2023-03-08
* @param {any} current 老fiber
* @param {any} workInProgress 新fiber
* @returns {any}
*/
export function beginWork(current, workInProgress) {
switch (workInProgress.tag) {
// 无论是函数组件还是类组件都是函数
case IndeterminateComponent:
// 这里是首次挂载时才走,走完后会区别出是类式组件还是函数组件,而且其中的执行步骤其实与下面的函数组件差不多,都需要走renderWithHooks
return mountIndeterminateComponent(current, workInProgress, workInProgress.type);
// 函数组件
case FunctionComponent: {
const Component = workInProgress.type;
const nextProps = workInProgress.pendingProps;
return updateFunctionComponent(current, workInProgress, Component, nextProps);
}
case HostRoot:
return updateHostRoot(current, workInProgress);
case HostComponent:
return updateHostComponent(current, workInProgress);
case HostText:
return null;
default:
return null;
}
};
在看整个流程图之前,先来看看几个全局变量。
- currentlyRenderingFiber: 当前函数组件对应的fiber
- workInProgressHook: 当前要执行的hook
- currentHook: 新的hook对应的老的hook
mountReducer流程图
这里就是构建hooks链表的关键。
updateReducer流程图
其实与mountReducer差不多。
总流程图
结尾
整篇文章篇幅不长,主旨是讲些关键点。但想单看本文理解整个React更新流程其实是很难的,所以在此再次建议各位读者调试一下以下仓库的代码,完成从beginWork到useReducer之后的commitRoot,相信各位读者一定能掌握React的更新流程。
本次代码仅包含到目前为止的useReducer,其中已经写好了demo,欢迎各位读者进行调试。
import * as React from 'react';
import { createRoot } from 'react-dom/client';
const reducer = (state, action) => {
if (action.type === 'add') return state + action.payload;
return state;
}
function FunctionComponent() {
const [number, dispatch] = React.useReducer(reducer, 0);
return (
<button
onClick={() => {
dispatch({ type: 'add', payload: 1 });
dispatch({ type: 'add', payload: 2 });
dispatch({ type: 'add', payload: 3 });
}}
>
{number}
</button>
);
};
const element = <FunctionComponent />;
const root = createRoot(document.getElementById('root'));
root.render(element);
效果:
仅到useReducer的代码仓库:useReducer
Reac18.2源码:点这里
转载自:https://juejin.cn/post/7240263755697897509