您现在的位置是:网站首页> 编程资料编程资料
React函数组件hook原理及构建hook链表算法详情_React_
2023-05-24
418人已围观
简介 React函数组件hook原理及构建hook链表算法详情_React_
写在前面的小结
- 每一个 hook 函数都有对应的 hook 对象保存状态信息
useContext是唯一一个不需要添加到 hook 链表的 hook 函数- 只有 useEffect、useLayoutEffect 以及 useImperativeHandle 这三个 hook 具有副作用,在 render 阶段需要给函数组件 fiber 添加对应的副作用标记。同时这三个 hook 都有对应的 effect 对象保存其状态信息
- 每次渲染都是重新构建 hook 链表以及 收集 effect list(fiber.updateQueue)
- 初次渲染调用 mountWorkInProgressHook 构建 hook 链表。更新渲染调用 updateWorkInProgressHook 构建 hook 链表并复用上一次的 hook 状态信息
Demo
可以用下面的 demo 在本地调试
import React, { useState, useEffect, useContext, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect, forwardRef, } from "react"; import ReactDOM from "react-dom"; const themes = { foreground: "red", background: "#eeeeee", }; const ThemeContext = React.createContext(themes); const Home = forwardRef((props, ref) => { debugger; const [count, setCount] = useState(0); const myRef = useRef(null); const theme = useContext(ThemeContext); useEffect(() => { console.log("useEffect", count); }, [count]); useLayoutEffect(() => { console.log("useLayoutEffect...", myRef); }); const res = useMemo(() => { console.log("useMemo"); return count * count; }, [count]); console.log("res...", res); useImperativeHandle(ref, () => ({ focus: () => { myRef.current.focus(); }, })); const onClick = useCallback(() => { setCount(count + 1); }, [count]); return ( {count} ); }); ReactDOM.render( , document.getElementById("root"));fiber
React 在初次渲染或者更新过程中,都会在 render 阶段创建新的或者复用旧的 fiber 节点。每一个函数组件,都有对应的 fiber 节点。
fiber 的主要属性如下:
var fiber = { alternate, child, elementType: () => {}, memoizedProps: null, memoizedState: null, // 在函数组件中,memoizedState用于保存hook链表 pendingProps: {}, return, sibling, stateNode, tag, // fiber的类型,函数组件对应的tag为2 type: () => {} updateQueue: null, }在函数组件的 fiber 中,有两个属性和 hook 有关:memoizedState 和updateQueue 属性。
- memoizedState 属性用于保存 hook 链表,hook 链表是单向链表。
- updateQueue 属性用于收集hook的副作用信息,保存
useEffect、useLayoutEffect、useImperativeHandle这三个 hook 的 effect 信息,是一个环状链表,其中 updateQueue.lastEffect 指向最后一个 effect 对象。effect 描述了 hook 的信息,比如useLayoutEffect的 effect 对象保存了监听函数,清除函数,依赖等。
hook 链表
React 为我们提供的以use开头的函数就是 hook,本质上函数在执行完成后,就会被销毁,然后状态丢失。React 能记住这些函数的状态信息的根本原因是,在函数组件执行过程中,React 会为每个 hook 函数创建对应的 hook 对象,然后将状态信息保存在 hook 对象中,在下一次更新渲染时,会从这些 hook 对象中获取上一次的状态信息。
在函数组件执行的过程中,比如上例中,当执行 Home() 函数组件时,React 会为组件内每个 hook 函数创建对应的 hook 对象,这些 hook 对象保存 hook 函数的信息以及状态,然后将这些 hook 对象连成一个链表。上例中,第一个执行的是useState hook,React 为其创建一个 hook:stateHook。第二个执行的是useRef hook,同样为其创建一个 hook:refHook,然后将 stateHook.next 指向 refHook:stateHook.next = refHook。同理,refHook.next = effectHook,...
需要注意:
useContext是唯一一个不会出现在 hook 链表中的 hook。- useState 是 useReducer 的语法糖,因此这里只需要用 useState 举例就好。
useEffect、useLayoutEffect、useImperativeHandle这三个 hook 都是属于 effect 类型的 hook,他们的 effect 对象都需要被添加到函数组件 fiber 的 updateQueue 中,以便在 commit 阶段执行。
上例中,hook 链表如下红色虚线中所示:

hook 对象及其属性介绍
函数组件内部的每一个 hook 函数,都有对应的 hook 对象用来保存 hook 函数的状态信息,hook 对象的属性如下:
var hook = { memoizedState,, baseState, baseQueue, queue, next, };注意,hook 对象中的memoizedState属性和 fiber 的memoizedState属性含义不同。next 指向下一个 hook 对象,函数组件中的 hook 就是通过 next 指针连成链表
同时,不同的 hook 中,memoizedState 的含义不同,下面详细介绍各类型 hook 对象的属性含义
useState Hook 对象
- hook.memoizedState 保存的是 useState 的 state 值。比如
const [count, setCount] = useState(0)中,memoizedState 保存的就是 state 的值。 - hook.queue 保存的是更新队列,是个环状链表。queue 的属性如下:
hook.queue = { pending: null, dispatch: null, lastRenderedReducer: basicStateReducer, lastRenderedState: initialState, };比如我们在 onClick 中多次调用setCount:
const onClick = useCallback(() => { debugger; setCount(count + 1); setCount(2); setCount(3); }, [count]);每次调用setCount,都会创建一个新的 update 对象,并添加进 hook.queue 中,update 对象属性如下:
var update = { lane: lane, action: action, // setCount的参数 eagerReducer: null, eagerState: null, next: null, };queue.pending 指向最后一个更新对象。
queue 队列如下红色实线所示:

在 render 阶段,会遍历 hook.queue,计算最终的 state 值,并存入 hook.memoizedState 中
useRef Hook
- hook.memoizedState 保存的是 ref 的值。
比如:
const myRef = useRef(null);
那么 memoizedState 保存的是 myRef 的值,即:
hook.memoizedState = { current, };useEffect、useLayoutEffect 以及 useImperativeHandle
- memoizedState 保存的是一个 effect 对象,effect 对象保存的是 hook 的状态信息,比如监听函数,依赖,清除函数等,
属性如下:
var effect = { tag: tag, // effect的类型,useEffect对应的tag为5,useLayoutEffect对应的tag为3 create: create, // useEffect或者useLayoutEffect的监听函数,即第一个参数 destroy: destroy, // useEffect或者useLayoutEffect的清除函数,即监听函数的返回值 deps: deps, // useEffect或者useLayoutEffect的依赖,第二个参数 // Circular next: null, // 在updateQueue中使用,将所有的effect连成一个链表 };这三个 hook 都属于 effect 类型的 hook,即具有副作用的 hook
- useEffect 的副作用为:Update | Passive,即 516
- useLayoutEffect 和 useImperativeHandle 的副作用都是:Update,即 4
在函数组件中,也就只有这三个 hook 才具有副作用,在 hook 执行的过程中需要给 fiber 添加对应的副作用标记。然后在 commit 阶段执行对应的操作,比如调用useEffect的监听函数,清除函数等等。
因此,React 需要将这三个 hook 函数的 effect 对象存到 fiber.updateQueue 中,以便在 commit 阶段遍历 updateQueue,执行对应的操作。updateQueue 也是一个环状链表,lastEffect 指向最后一个 effect 对象。effect 和 effect 之间通过 next 相连。
const effect = { create: () => { console.log("useEffect", count); }, deps: [0] destroy: undefined, tag: 5, } effect.next = effect fiber.updateQueue = { lastEffect: effect, };fiber.updateQueue 如下图红色实线所示:

hook 对应的 effect 对象如下图红色实线所示:

useMemo
- hook.memoizedState 保存的是 useMemo 的值和依赖。比如:
const res = useMemo(() => { return count * count; }, [count]);那么 memoizedState 保存的是返回值以及依赖,即:
hook.memoizedState = [count * count, [count]];
useCallback
hook.memoizedState 保存的是回调函数和依赖,比如:
const onClick = useCallback(callback dep);
那么 memoizedState=[callback, dep]
构建 Hook 链表的源码
React 在初次渲染和更新这两个过程,构建 hook 链表的算法不一样,因此 React 对这两个过程是分开处理的:
var HooksDispatcherOnMount = { useCallback: mountCallback, useContext: readContext, useEffect: mountEffect, useImperativeHandle: mountImperativeHandle, useLayoutEffect: mountLayoutEffect, useMemo: mountMemo, useRef: mountRef, useState: mountState, }; var HooksDispatcherOnUpdate = { useCallback: updateCallback, useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useRef: updateRef, useState: updateState, };如果是初次渲染,则使用HooksDispatcherOnMount,此时如果我们调用 useState,实际上调用的是HooksDispatcherOnMount.useState,执行的是mountState方法。
如果是更新阶段,则使用
HooksDispatcherOnUpdate,此时如果我们调用 useState,实际上调用的是HooksDispatch
相关内容
- JavaScript树形数据结构处理_javascript技巧_
- 深入了解JavaScript中let/var/function的变量提升_javascript技巧_
- vue实现列表展示示例详解_vue.js_
- vue中window.addEventListener(‘scroll‘, xx)失效的解决_vue.js_
- 关于Vue3路由push跳转问题(解决Vue2this.$router.push失效)_vue.js_
- Vue中如何把hash模式改为history模式_vue.js_
- 关于Vue3父子组件emit参数传递问题(解决Vue2this.$emit无效问题)_vue.js_
- 一文详解Vue3中使用ref获取元素节点_vue.js_
- vue3 封装自定义组件v-model的示例_vue.js_
- Vue的filters(本地)或filter(全局)过滤常用数据类型解析_vue.js_
