React 16.8 中新加入了 Hook 特性,如果你还没有用过 Hook,可以参考官方文档先进行了解,不在此赘述了。
Hook 可以让我们在不编写 class 组件的情况下使用状态等 React 特性。纯函数组件是没有状态的,也不能执行生命周期函数,所以使用场景比较有限,而 class 组件什么功能都有,只不过比较重,这其实并不太符合 React 的理念,所以 Hooks 就诞生了。
Hooks 原意为钩子,对于纯函数组件来说,如果需要什么状态或是什么副作用功能,那么我们就用钩子把他们钩进来。
从一个极简 Hooks 开始
实现一个不到100行代码的极简 useState Hook。
首先看一个例子:
function App() {
const [num, setNum] = useState(0);
return <p onClick={() => setNum(num => num + 1)}>{num}</p>
}
这个组件做的事情比较简单:
- 组件初始化挂载(mount),
useState会用初始值进行渲染,如果是更新(update),则会使用之前已经生成好的num的值; - 点击
p标签触发setNum,num => num + 1将被执行,这也是一个更新(update)过程。
Hook 的数据结构
先说明一下,一个 hook 对象和一个 useState 对应,所以我们使用 useState 的时候就相当于创建出了一个 hook 对象:
hook = {
// 保存 hook 的更新队列
queue: {
pending: null
},
// 保存 hook 对应的 state
memoizedState: initialState,
// 与下一个 Hook 连接形成单向无环链表
next: null
}
解释一下对象中的几个字段:
- queue 是一个队列,我们每调用一次
setNum就会在这个队列中加入一个新的update - memoizedState 用来存储状态的值
- next 是一个单向无环链表,指向下一个
hook,上面的例子只创建了一个hook,实际项目中可能有很多个
更新(update)如何串联
代码里面每次对状态进行 set 操作都会形成一个更新(update),每一个更新(update)通过一个环形单向链表结构进行串联。
单个 update 数据结构如下:
const update = {
// 更新执行的函数
action,
// 与同一个 Hook 的其他更新形成链表
next: null
}
调用 setNum 实际上调用的是下面这个函数:
// 调用方式
dispatchAction.bind(null, hook.queue)
// 函数实现
function dispatchAction(queue, action) {
// 创建 update
const update = {
action,
next: null
};
// 环状单向链表操作
if (queue.pending === null) { // 当前 hook 的更新队列中还没有正在处理的更新
update.next = update; // 把当前更新的 next 指向当前更新,形成环
} else { // 当前 hook 的更新队列中有正在处理的更新
update.next = queue.pending.next; // 将当前更新的 next 指针指向当前更新队列中正在处理的更新的 next
queue.pending.next = update; // 将当前更新队列中正在处理的更新的 next 指向当前更新,形成环
}
queue.pending = update; // 把新创建的更新作为当前 hook 的更新队列中正在处理的更新
// 模拟React开始调度更新
schedule();
}
这个单向环形链表不太好理解,将前面的例子改造并分析一下:
增加几个更新过程,形成新的例子:
function App() {
const [num, setNum] = useState(0);
return <p onClick={() => {
setNum(num => num + 1);
setNum(num => num + 1);
}}>{num}</p>
}
执行第一个 setNum 的时候,queue.pending === null 成立,执行 update.next = update 和 queue.pending = update,此时数据结构如下图所示:
执行第二个 setNum 的时候,queue.pending === null 不成立,执行 update.next = queue.pending.next 和 queue.pending.next = update,此时数据结构如下图所示:
这样的结构有个好处就是在有多个 update 的情况下, queue.pending 始终指向最后一个插入的 update,遍历 update 的时候 queue.pending.next 指向第一个插入的 update。
状态如何保存
class 组件的实例可以存储数据,但是函数组件并不能这样干,函数组件会将状态都存入到对应的 fiber 中。
精简后的结构如下:
// App 组件对应的 fiber 对象
const fiber = {
// 保存该函数对应的 Hooks 链表
memoizedState: null,
// 指向 App 函数,App 函数执行之后渲染返回的结果是 DOM 节点
// 所以这个属性也可以理解为保存的是该 fiber 对应的 DOM 节点
stateNode: App
};
调度更新
前面的 dispatchAction 函数中,调用了一个 schedule 函数,这就是用于调度更新的函数。下面是实现思路:
- 用
isMount变量(全局变量)来代指mount还是update。 - 通过
workInProgressHook变量(全局变量)指向当前正在工作的hook。 - 执行
fiber的stateNode方法,触发组件重新渲染,生成新的 DOM 节点。 - 组件首次
render为mount,以后再触发的更新为update。 - 在组件
render时,每当遇到下一个useState,我们移动workInProgressHook的指针。
// 首次 render 时是 mount
let isMount = true;
function schedule() {
// 更新前将 workInProgressHook 重置为 fiber 保存的第一个 Hook
workInProgressHook = fiber.memoizedState;
// 触发组件 render
fiber.stateNode();
// 组件首次 render 为 mount,以后再触发的更新为 update
isMount = false;
}
// 这是一个 Hook
let workInProgressHook;
// 用于切换 workInProgressHook 的指针,实际在 useState 函数内部有条件地调用执行
workInProgressHook = workInProgressHook.next;
每个不同的 Hook 都是在被串在一个链表中,前面说了一个 useState 创建一个 Hook, 所以组件每次渲染的时候 useState 的调用顺序及数量保持一致,就可以通过 workInProgressHook 找到当前 useState 对应的 Hook 对象。
useState 实现
直接上代码:
function useState(initialState) {
let hook;
if (isMount) { // 首次挂载
// 初始化 hook
hook = {
queue: {
pending: null
},
memoizedState: initialState,
next: null
}
if (!fiber.memoizedState) {
// 当前 fiber 没有存 hook,就把当前 hook 存进去
fiber.memoizedState = hook;
} else {
// 当前 fiber 已经存 hook 了
// 就把当前 hook 存到 hook 链表的 next 节点中(一个函数组件中定义了多个 hook 的情况)
workInProgressHook.next = hook;
}
// 切换工作 hook 为当前 hook
workInProgressHook = hook;
} else { // 非首次挂载(更新的时候触发此逻辑)
// 缓存工作 hook(作为上一个的 hook)
hook = workInProgressHook;
// 切换工作 hook
workInProgressHook = workInProgressHook.next;
}
// 更新执行前的初始状态
let baseState = hook.memoizedState;
if (hook.queue.pending) { // 不为空代表存在更新
// 取出第一个更新
let firstUpdate = hook.queue.pending.next;
// 执行更新队列中的所有更新
do {
// 要执行的动作,比如:setNum 中的 num => num + 1
const action = firstUpdate.action;
// 入参为初始状态,执行动作输出新状态,此处不是函数可能会有问题,精简版未支持
baseState = action(baseState);
// 切换到下一个更新
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending.next)
// 更新队列执行完后清空
hook.queue.pending = null;
}
// 更新 hook 的状态值
hook.memoizedState = baseState;
// 返回
return [baseState, dispatchAction.bind(null, hook.queue)];
}
完整代码
通过调用 App 返回的 click 方法模拟组件 click 的行为。调用 window.app.click() 模拟组件点击事件。
let workInProgressHook;
let isMount = true;
const fiber = {
memoizedState: null,
stateNode: App
};
function schedule() {
workInProgressHook = fiber.memoizedState;
const app = fiber.stateNode();
isMount = false;
return app;
}
function dispatchAction(queue, action) {
const update = {
action,
next: null
}
if (queue.pending === null) {
update.next = update;
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
schedule();
}
function useState(initialState) {
let hook;
if (isMount) {
hook = {
queue: {
pending: null
},
memoizedState: initialState,
next: null
}
if (!fiber.memoizedState) {
fiber.memoizedState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else {
hook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
let baseState = hook.memoizedState;
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending)
hook.queue.pending = null;
}
hook.memoizedState = baseState;
return [baseState, dispatchAction.bind(null, hook.queue)];
}
function App() {
const [num, updateNum] = useState(0);
console.log(`${isMount ? 'mount' : 'update'} num: `, num);
return {
click() {
updateNum(num => num + 1);
}
}
}
window.app = schedule();
与 React 的差异
毕竟是精简版的 Hooks,和 React 相比还是少了很多功能:
React Hooks没有使用isMount变量,而是在不同时机使用不同的dispatcher。换言之,mount时的useState与update时的useState不是同一个函数。React Hooks有中途跳过更新的优化手段。React Hooks有batchedUpdates,当在click中触发三次setNum,如果setNum的参数是数字等非函数类型精简版会触发三次更新,而React只会触发一次,另外精简版也不支持setNum的参数为非函数。React Hooks的update有优先级概念,可以跳过不高优先的update。
源码
Hooks 从哪儿来
还是从 useState 入手来看源码,
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function App() {
const [count, setCount] = useState(0); // 断点打到这儿进行调试
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
可以看到进入到了 hooks 定义的地方:
// path: packages/react/src/ReactHooks.js
// 省略一些代码
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
// 省略一些代码
可以看到 resolveDispatcher 函数,我们使用的 hooks 都是从这个函数返回的,不过当我们去看这个函数的定义的时候会惊奇的发现,就这:
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher),
};
export default ReactCurrentDispatcher;
所以,这些 hooks 都是哪儿来的呢?
我来找一下给 ReactCurrentDispatcher.current 赋值的地方:
// path: packages/react-reconciler/src/ReactFiberHooks.new.js
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
// 省略一些代码
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
if (__DEV__) {
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
} else {
// 是 mount 还是 update 在此判断
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
// 执行函数组件
let children = Component(props, secondArg);
// 省略一些代码
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// 省略一些代码
return children;
}
从上面代码可以看出 mount 和 update 时调用的是不同的 hook。
HooksDispatcherOnMount 和 HooksDispatcherOnUpdate 生产模式下主要还是用这俩,其定义如下:
// path: packages/react-reconciler/src/ReactFiberHooks.new.js
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
useOpaqueIdentifier: mountOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
// 省略一些代码
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
useOpaqueIdentifier: updateOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
// 省略一些代码
最后执行了 ReactCurrentDispatcher.current = ContextOnlyDispatcher;,ContextOnlyDispatcher 的定义如下:
// path: packages/react-reconciler/src/ReactFiberHooks.new.js
export const ContextOnlyDispatcher: Dispatcher = {
readContext,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useMutableSource: throwInvalidHookError,
useOpaqueIdentifier: throwInvalidHookError,
unstable_isNewReconciler: enableNewReconciler,
};
从这里可以看出,执行函数组件后,将 hook 都指向了 throwInvalidHookError,就是抛出错误的函数,这时再调用 hook 就会报错,比如下面这种情况:
useEffect(() => {
useState(0);
});
下面再来看一下 renderWithHooks 函数是怎么调进来的,从源码上看主要是两个地方调用他:
- updateFunctionComponent 更新函数组件
- updateForwardRef 更新
forwardRef包裹的函数组件,一般和useImperativeHandle一起使用,用于暴露组件内部方法供外部调用
这两个函数都是在 beginWork 函数的 switch ... case 中调用的。
下图是 hooks 的大致调用过程:
至此我们解决了 hooks 是从哪儿来的问题了,下面来看一下 hooks 都是具体是什么。
Hook 的数据结构
这里指单个 hook 的数据结构,比如:const [count, setCount] = useState(0); 定义的一个状态就是这里说的单个 hook。
// path: packages/react-reconciler/src/ReactFiberHooks.new.js
export type Hook = {|
memoizedState: any,
baseState: any,
baseQueue: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
next: Hook | null,
|};
- memoizedState 保存着单一
hook的对应数据(与 fiber 的 memorizedState 不一样,fiber 那个保存的是函数组件的Hooks链表) - queue 更新队列
- next 下一个
hook - baseState 初始 state
- baseQueue 初始 queue 队列
不同类型 hook 的 memoizedState 会有所不同:
- useState 对于
const [state, updateState] = useState(initialState),memoizedState保存state的值 - useReducer 对于
const [state, dispatch] = useReducer(reducer, {});,memoizedState保存state的值 - useEffect
memoizedState保存包含useEffect回调函数、依赖项等的链表数据结构effect - useRef 对于
useRef(1),memoizedState保存{current: 1} - useMemo:对于
useMemo(callback, [depA]),memoizedState保存[callback(), depA] - useCallback:对于
useCallback(callback, [depA]),memoizedState保存[callback, depA]。与useMemo的区别是,useCallback保存的是callback函数本身,而useMemo保存的是callback函数的执行结果 - useContext 没有
memoizedState
下图是一个完整的数据结构:
useState 与 useReducer
由前文可知调用 hook 的时候分为两个场景,一个是 mount,一个是 update。
mount
// path: packages/react-reconciler/src/ReactFiberHooks.new.js
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 创建并返回当前 hook
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
// 赋值初始 state
hook.memoizedState = hook.baseState = initialState;
// 创建 queue
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
// 创建 dispatch
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 创建并返回当前的 hook
const hook = mountWorkInProgressHook();
// 赋值初始 state
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
hook.memoizedState = hook.baseState = initialState;
// 创建 queue
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
});
// 创建 dispatch
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
// 创建并返回对应 hook
function mountWorkInProgressHook(): Hook {
// 新建一个 hook 对象
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
// 把新建的 hook 对象挂到链表上
if (workInProgressHook === null) {
// 还没有 hook 链表,就将 fiber 的 memoizedState 属性指向 hook
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 如果已经有链表了,就挂在当前链表的 next 上
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
可以看出 mount 场景下,useState 和 useReducer 做的事情基本上是差不多的,都是先创建 hook,然后赋值输出状态,创建更新队列,最后创建 dispatch 并返回。
值得注意的是 queue,他们两个 hook 的 queue 有细微的差异,在 lastRenderedReducer 属性:
const queue = (hook.queue = {
// 更新队列中正在处理的更新
pending: null,
interleaved: null,
lanes: NoLanes,
// 保存 dispatchAction.bind() 的值
dispatch: null,
// useState 是 lastRenderedReducer: basicStateReducer,
// 上一次 render 时使用的 reducer
lastRenderedReducer: reducer,
// 上一次 render 时的 state
lastRenderedState: (initialState: any),
});
// 下面典型案例执行的关键代码
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
可见,useState 即 reducer 参数为 basicStateReducer 的 useReducer。
update
// path: packages/react-reconciler/src/ReactFiberHooks.new.js
type Update<S, A> = {|
lane: Lane,
action: A, // 修改动作
eagerReducer: ((S, A) => S) | null, // 下一个 reducer
eagerState: S | null, // 下一次的 state
next: Update<S, A>, // 下一个 update
|};
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 获取当前 hook
const hook = updateWorkInProgressHook();
const queue = hook.queue;
invariant(
queue !== null,
'Should have a queue. This is likely a bug in React. Please file an issue.',
);
queue.lastRenderedReducer = reducer;
// 中间有一大堆链表操作,执行了 hook 的 queue 中的 update
// 省略一些代码
// 下面典型案例执行的关键代码
const action = update.action;
newState = reducer(newState, action);
// 省略一些代码
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
很明显 useState 和 useReducer 在 update 场景下调用的是同一个函数 updateReducer。
updateReducer 的流程比较复杂,尤其里面有一大堆难懂的链表操作,整个流程可以概括为一句话:找到对应的 hook,根据 update 计算该 hook 的新 state 并返回。
有一个点值得注意:
mount 时获取当前 hook 使用的是 mountWorkInProgressHook,而 update 时使用的是 updateWorkInProgressHook,这两个函数有一些不一样,这里的原因是:
mount时可以确定是调用ReactDOM.render或相关初始化API产生的更新,只会执行一次。update可能是在事件回调或副作用中触发的更新或者是render阶段触发的更新,为了避免组件无限循环更新,后者需要区别对待。
dispatchAction
mount 和 update 场景之后都调用了 dispatchAction 函数
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
// 省略一些代码
// 创建 update 对象
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// render阶段触发的更新
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
// 把新 update 加入更新队列,并生成环形链表
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
} else {
if (isInterleavedUpdate(fiber, lane)) {
const interleaved = queue.interleaved;
if (interleaved === null) {
update.next = update;
pushInterleavedQueue(queue);
} else {
update.next = interleaved.next;
interleaved.next = update;
}
queue.interleaved = update;
} else {
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
// 省略一些代码
// 进行调度
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
// 省略一些代码
}
// 省略一些代码
}
典型使用场景
状态多次设置只有最后一次生效:
function App() {
const [num, setNum] = useState(0);
const handleClick = () => {
setNum(1);
setNum(2);
setNum(3);
};
return <p onClick={handleClick}>{num}</p>
}
function App() {
const [num, setNum] = useState(0);
const handleClick = () => {
setNum(num + 1);
setNum(num + 1);
setNum(num + 1);
};
return <p onClick={handleClick}>{num}</p>
}
这两种情况都只有最后一次生效:前面一个是因为在 update 队列被遍历执行的过程中如果更新函数里面传入的只是个值的话前面执行了会直接把前面的值覆盖;后面一个是因为 状态 num 在每次执行 setNum 的时候并不会里面在该函数组件中更新,所以这三次 setNum 的 num 都是同一个值。
状态设置的时候传入函数,每次都生效:
function App() {
const [num, setNum] = useState(0);
const handleClick = () => {
setNum(preVal => preVal + 1);
setNum(preVal => preVal + 1);
setNum(preVal => preVal + 1);
};
return <p onClick={handleClick}>{num}</p>
}
这种情况每次执行 setNum 都是生效的,因为我们传入的是一个函数,函数在处理的时候会把每一次 update 队列的最新状态拿到,这在 updateReducer 函数中也是有体现的。
总结
Hooks是挂在对应Fiber的memoizedState上的;- 一个函数组件中的多个
Hook相互之间由一个链表串起来的,所以每次组件渲染的时候要保证顺序及数量一致才能让workInProgressHook找到当前useState对应的Hook对象,这也会useState不能放在if等条件中的原因; - 某个
Hook的更新可以被多次调用,每次调用都会产生一个update对象,而这些对象是以一个环形链表的数据结构存在Hook的queue.pending中的,queue.pending始终指向最后一个插入的update对象,遍历update的时候queue.pending.next指向第一个插入的update对象; Hook更新的时候调用dispatchAction.bind(null, fiber, queue)函数,其在执行的时候保持了对fiber和hook的引用,从而不会被js引擎的垃圾回收销毁,所以函数组件才能保存住状态;Hook的更新函数被调用之后会触发组件重新渲染,重新渲染的时候会按顺序依次执行所有的更新(hook 的 queue 里面的 update 对象);useState可以理解为useReducer的一个特例;- 我们通常认为,
useReducer(reducer, initialState)的传参为初始化参数,在以后的调用中都不可变,但是在updateReducer方法中,可以看到lastRenderedReducer在每次调用时都会重新赋值也就是说,reducer参数是随时可变的。