useContext
是唯一一个不需要添加到 hook 链表的 hook 函数可以用下面的 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"));
React 在初次渲染或者更新过程中,都会在 render 阶段创建新的或者复用旧的 fiber 节点。每一个函数组件,都有对应的 fiber 节点。
fiber 的主要属性如下:
var fiber = {
alternate,
child,
elementType: () => {
},
memoizedProps: null,
memoizedState: null, // 在函数组件中,memoizedState用于保存hook链表
pendingProps: {
},
return,
sibling,
stateNode,
tag, // fiber的节点类型,初次渲染时,函数组件对应的tag为2,后续更新过程中对应的tag为0
type: () => {
}
updateQueue: null,
}
在函数组件的 fiber 中,有两个属性和 hook 有关:memoizedState
和updateQueue
属性。
useEffect
、useLayoutEffect
、useImperativeHandle
这三个 hook 的 effect 信息,是一个环状链表,其中 updateQueue.lastEffect 指向最后一个 effect 对象。effect 描述了 hook 的信息,比如useLayoutEffect
的 effect 对象保存了监听函数,清除函数,依赖等。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。useEffect
、useLayoutEffect
、useImperativeHandle
这三个 hook 都是属于 effect 类型的 hook,他们的 effect 对象都需要被添加到函数组件 fiber 的 updateQueue 中,以便在 commit 阶段执行。上例中,hook 链表如下红色虚线中所示:
函数组件内部的每一个 hook 函数,都有对应的 hook 对象用来保存 hook 函数的状态信息,hook 对象的属性如下:
var hook = {
memoizedState,,
baseState,
baseQueue,
queue,
next,
};
注意,hook 对象中的memoizedState
属性和 fiber 的memoizedState
属性含义不同。next
指向下一个 hook 对象,函数组件中的 hook 就是通过 next 指针连成链表
同时,不同的 hook 中,memoizedState 的含义不同,下面详细介绍各类型 hook 对象的属性含义
const [count, setCount] = useState(0)
中,memoizedState 保存的就是 state 的值。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,