React源码分析6-hooks源码

本文深入探讨React Hooks的执行过程,包括相关数据结构如Hook、Update & UpdateQueue、Effect,以及useState、useEffect、useRef等常用Hooks的源码分析。详细解析了函数组件的更新流程,从引入Hooks到不同阶段的Hook更新,揭示了Hooks如何在React中工作。
摘要由CSDN通过智能技术生成

本文将讲解 hooks 的执行过程以及常用的 hooks 的源码。

hooks 相关数据结构

要理解 hooks 的执行过程,首先想要大家对 hooks 相关的数据结构有所了解,便于后面大家顺畅地阅读代码。

Hook

每一个 hooks 方法都会生成一个类型为 Hook 的对象,用来存储一些信息,前面提到过函数组件 fiber 中的 memoizedState 会存储 hooks 链表,每个链表结点的结构就是 Hook。

// packages/react-reconciler/src/ReactFiberHooks.old.js

export type Hook = {
   |
  memoizedState: any, // 上次渲染时所用的 state
  baseState: any, // 已处理的 update 计算出的 state
  baseQueue: Update<any, any> | null, // 未处理的 update 队列(一般是上一轮渲染未完成的 update)
  queue: UpdateQueue<any, any> | null, // 当前出发的 update 队列
  next: Hook | null, // 指向下一个 hook,形成链表结构
|};

举个例子,我们通过函数组件使用了两个 useState hooks:

const [name, setName] = useState('小科比');
const [age, setAge] = useState(23);

则实际的 Hook 结构如下:

{
   
  memoizedState: '小科比',
  baseState: '小科比',
  baseQueue: null,
  queue: null,
  next: {
   
    memoizedState: 23,
    baseState: 23,
    baseQueue: null,
    queue: null,
  },
};

不同的 hooks 方法,memoizedState 存储的内容不同,常用的 hooks memoizedState 存储的内容如下:

  • useState: state
  • useEffect: effect 对象
  • useMemo/useCallback: [callback, deps]
  • useRef: { current: xxx }

Update & UpdateQueue

Update 和 UpdateQueue 是存储 useState 的 state 及 useReducer 的 reducer 相关内容的数据结构。

// packages/react-reconciler/src/ReactFiberHooks.old.js

type Update<S, A> = {
   |
  lane: Lane, // 优先级
  // reducer 对应要执行的 action
  action: A,
  // 触发 dispatch 时的 reducer
  eagerReducer: ((S, A) => S) | null,
  // 触发 dispatch 是的 state
  eagerState: S | null,
  // 下一个要执行的 Update
  next: Update<S, A>,
  // react 的优先级权重
  priority?: ReactPriorityLevel,
|};

type UpdateQueue<S, A> = {
   |
  // 当前要触发的 update
  pending: Update<S, A> | null,
  // 存放 dispatchAction.bind() 的值
  dispatch: (A => mixed) | null,
  // 上一次 render 的 reducer
  lastRenderedReducer: ((S, A) => S) | null,
  // 上一次 render 的 state
  lastRenderedState: S | null,
|};

每次调用 setState 或者 useReducer 的 dispatch 时,都会生成一个 Update 类型的对象,并将其添加到 UpdateQueue 队列中。

例如,在如下的函数组件中:

const [name, setName] = useState('小科比');
setName('大科比');

当我们点击 input 按钮时,执行了 setName(),此时对应的 hook 结构如下:

{
   
  memoizedState: '小科比',
  baseState: '小科比',
  baseQueue: null,
  queue: {
   
    pending: {
   
      lane: 1,
      action: '大科比',
      eagerState: '大科比',
      // ...
    },
    lastRenderedState: '小科比',
    // ...
  },
  next: null,
};

最后 react 会遍历 UpdateQueue 中的每个 Update 去进行更新。相关参考视频讲解:进入学习

Effect

Effect 结构是和 useEffect 等 hooks 相关的,我们看一下它的结构:

// packages/react-reconciler/src/ReactFiberHooks.old.js

export type Effect = {
   |
  tag: HookFlags, // 标记是否有 effect 需要执行
  create: () => (() => void) | void, // 回调函数
  destroy: (() => void) | void, // 销毁时触发的回调
  deps: Array<mixed> | null, // 依赖的数组
  next: Effect, // 下一个要执行的 Effect
|};

当我们的函数组件中使用了如下的 useEffect 时:

useEffect(() => {
   
  console.log('hello');
  return () => {
   
    console.log('bye');
  };
}, []);

对应的 Hook 如下:

{
   
  memoizedState: {
   
    create: () => {
    console.log('hello') },
    destroy: () => {
    console.log('bye') },
    deps: [],
    // ...
  },
  baseState: null,
  baseQueue: null,
  queue: null,
  next: null,
}

执行过程

下面我们探索一下 hooks 在 react 中具体的执行流程。

引入 hooks

我们以一个简单的 hooks 写法的 react 应用程序为例去寻找 hooks 源码:

import {
    useState } from 'react';

export default function App() {
   
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>{
   count}</p>
      <input
        type="button"
        value="增加"
        onClick={
   () => {
             setCount(count + 1);        }}      />    </div>
  );
}

根据引入的 useState api,我们找到 react hooks 的入口文件:

// packages/react/src/ReactHooks.js

function resolveDispatcher() {
   
  const dispatcher = ReactCurrentDispatcher.current;
  // ...
  return dispatcher;
}

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
   
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

// ...

根据上面的源码我们可以知道,所有的 hooks api 都是挂载在 resolveDispatcher 中返回的 dispatcher 对象上面的,也就是挂载在 ReactCurrentDispatcher.current 上面,那么我们再继续去看一下 ReactCurrentDispatcher 是什么:

// packages/react/src/ReactCurrentDispatcher.js

import type {
   Dispatcher} from 'react-reconciler/src/ReactInternalTypes';

const ReactCurrentDispatcher = {
   
  current: (null: null | Dispatcher),
};

export default ReactCurrentDispatcher;

到这里我们的线索就断了,ReactCurrentDispatcher 上只有一个 current 用于挂在 hooks,但是 hooks 的详细源码以及 ReactCurrentDispatcher 的具体内容我们并没有找到在哪里,所以我们只能另寻出路,从 react 的执行过程去入手。

函数组件更新过程

我们的 hooks 都是在函数组件中使用的,所以让我们去看一下 render 过程关于函数组件的更新。render 过程中的调度是从 beginWork 开始的,来到 beginWork 的源码后我们可以发现,针对函数组件的渲染和更新,使用了 updateFunctionComponent 函数:

// packages/react-reconciler/src/ReactFiberBeginWork.old.js

function beginWork(
  current: Fiber | null,  workInProgress: Fiber,  renderLanes: Lanes,
): Fiber | null {
   
  // ...
  switch (workInProgress.tag) {
   
    // ...
    case FunctionComponent: {
   
      // ...
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    // ...
  }
  // ...
}

那我们在继续看一下 updateFunctionComponent 函数的源码,里面调用了 renderWithHooks 函数,这便是函数组件更新和渲染过程执行的入口:

// packages/react-reconciler/src/ReactFiberBeginWork.old.js

function 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值