概述
React 在 v16.8 提供了 Hook 特性,React Hooks 通过增强函数式组件,为 Function Component 注入一些功能,例如 useState
让原本的 Stateless Function Component 有了状态。
工作原理
接下来我们从 useState
这个 hook 为切入点,打开 React Hooks 源码看看背后的原理。
先看个 Demo:
import React, { useState } from 'react';
function App() {
const [count, dispatchCount] = useState(0);
return (
<div>
<span>{count}</span>
<button onClick={() => dispatchCount(count + 1)}>
increment
</button>
</div>
)
}
上述 Demo 使用函数组件定义了一个计数器,相对于普通函数组件,该组件提供了 count
的状态,每点击按钮一次 count 就加 1。
接下来我们看下 useState
的源码,它是如何保存 count 状态、更新 count 状态的。
状态保存与更新
去除无关代码之后,可以看到我们调用的 useState
只是一个入口,最终是调用 dispatcher
的一个方法。并且在 React.js 中只负责定义。
// react/src/ReactHooks.js
const ReactCurrentDispatcher = {
/**
* @internal
* @type {ReactComponent}
*/
current: (null: null | Dispatcher),
};
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
Hook 只有在 FunctionalComponent
更新的时候才会被调用,在 updateFunctionComponent
的方法中找到了 Hook 更新的入口 renderWithHooks
,在 renderWithHooks
中依照条件对 ReactCurrentDispatcher.current
进行了赋值。
// react-reconciler/src/ReactFiberHooks.js
function renderWithHooks(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
props: any,
secondArg: any,
nextRenderExpirationTime: ExpirationTime,
) {
// ... 省略无关代码
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
// Mount
? HooksDispatcherOnMount
// Update
: HooksDispatcherOnUpdate;
// ...
}
可以看到 Dispatcher 分为 Mount 和 Update。这里我们可以找到对应的 mountState
和 updateState
,其他 hook 也是这么分类的,如果我们要查看其他 hook 代码,均可以在这里找到对应时机的代码。
状态保存
在了解具体代码之前,我们先了解下 Hook
的定义.
Hook = {
// 当前 hook 的 state,就是上述 Demo 中的 count 值
memoizedState: any,
// 多次调用,保存队列
queue: UpdateQueue<any, any> | null,
// 下一个 hook,通过该属性连接成一个 hook 的单向链表
next: Hook | null,
|};
对于一个组件内的 hook 对象,会被保存在 App 组件对应的 Fiber 对象的 memoizedState
中。保存结构大致如下:
fiber = {
memoizedState: {
memoizedState: initialState,
queue: {},
next: {
memoizedState: initialState,
queue: {},
next: null,
},
}
}
状态更新
当首次渲染时,调用 mountState
时,返回 [hook.memoizedState, dispatch]
。
// react-reconciler/src/ReactFiberHooks.js
// 获取 hook 对象
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// 将 hook 加到 fiber.memoizedState
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 插入链表,指定下一个 hook
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
调用上述 Demo 中 dispatchCount
,其实就是调用 dispatchAction.bind(null, currentlyRenderingFiber, queue)
,
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
// 每调用一次 dispatchCount,都会创建一个 update 对象,记录要更新的值 action
const update: Update<S, A> = {
action,
next: null,
// ...
};
// ...
// 将更新附加到列表的末尾
const pending = queue.pending;
if (pending === null) {
// 这是第一次更新,创建一个循环列表。
update.next = update;
} else {
// 插入新的 update 节点
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
// ...
// 更新渲染调度
scheduleWork()
}
更新 state
当调用 dispatchCount 时,这时候实际是调用 updateState
对 state 进行合并处理,在 updateReducer
中会遍历 hook 链表,得到最新 memoizedState
并返回。
// react-reconciler/src/ReactFiberHooks.js
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,
) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
// ...
let first = baseQueue.next;
do {
// 获取传入的 state action
const action = update.action;
// 更新 state
newState = reducer(newState, action);
// 遍历下一个更新 action
update = update.next;
} while (update !== null && update !== first)
hook.memoizedState = newState;
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
小结
对于 useState
的逻辑,就相当于原有的 class 组件的 state,只是在函数组件中,他的状态存在 Fiber 节点上。通过链表操作遍历更新该 Fiber 节点下的 hook 对象来更新函数组件中的 state。