likes
comments
collection
share

React源码系列(五)------ useReducer

作者站长头像
站长
· 阅读数 73

前言

本文是该系列第五篇,经过了上一篇的事件系统和本文的状态定义和修改,我们正式进入到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 });
}}

React源码系列(五)------ useReducer

这里之所以要形成这样一个链表,是为了批量更新(批量更新相关内容后续在lane优先级中讲解)做准备,我们知道在同一个执行上下文多次调用setState他只会执行一次调度,而这次调度能计算出多个setState后的准确的值就要依赖这个链表。

hooks调用原理图

根据上面的信息,我们可以整理出一个这样的大致的原理图。

React源码系列(五)------ useReducer

图中为一个函数组件的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流程图

React源码系列(五)------ useReducer

这里就是构建hooks链表的关键。

React源码系列(五)------ useReducer

updateReducer流程图

其实与mountReducer差不多。

React源码系列(五)------ useReducer

总流程图

React源码系列(五)------ useReducer

结尾

整篇文章篇幅不长,主旨是讲些关键点。但想单看本文理解整个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);

效果:

React源码系列(五)------ useReducer

仅到useReducer的代码仓库:useReducer

Reac18.2源码:点这里

上一篇:事件系统

转载自:https://juejin.cn/post/7240263755697897509
评论
请登录