一、基础回顾
1、useState
该hook用于给函数组件添加状态变量;
js
const [state, setState] = useState(initialState);
(1)参数 - initialState
initialState
用于设置组件的初始化状态,该初始值只有在组件首次渲染时使用。
(2)返回值
该hook返回一组值:[state, setState]
state
:状态值;每调用一次useState,返回的都是一个新的state变量;setState
:该函数用于更新状态值,并触发组件的重新渲染;useState
的更新是异步的,在调用setState
时,并不会立即更新状态,而是把一次重新渲染添加到队列中,React会在合适的时机处理这次更新;
2、为什么要引入useState?
- 局部变量无法在多次渲染中持久保持;
- React在渲染函数式组件时,每次都会从头开始渲染,不会考虑之前对局部变量的任何更改;
- 更改局部变量不会触发组件的重新渲染;
二、useState的原理
Code示例
js
import {useState, useRef, useCallback, useEffect} from 'react';
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('wxm');
const ref = useRef('ref');
console.log('组件渲染了');
const handleClick = useCallback(() => {
setCount(count + 1);
setCount(count + 1);
console.log('------');
setName('Amy');
setCount(count => count + 1);
}, []);
useEffect(() => {
if (name === 'Amy') {
setCount(count => count + 1);
}
}, [name]);
return (
<div>
<button onClick={handleClick}>更改</button>
<p>
{/* 在React中,当前标签包含5个子元素:当前的name值为:{name} 这是两个子元素 */}
当前的name值为:{name}
<br/>
当前的count值为:{count}
</p>
{/* 一般不建议将ref作为渲染在页面中的内容 */}
{ref.current}
</div>
);
};
export default App;
useState Hook的定义
当我们在函数组件中调用useState
时,useState
到底是怎样的一个定义呢?它在源码中其实是这样的:
js
export function useState<S>(
initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
可以看到,它实际上调用的是dispatcher对象的useState函数;
dispatcher对象
js
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return ((dispatcher: any): Dispatcher);
}
// ReactCurrentDispatcher.current的赋值
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HookDispatcherOnMount
: HookDispatcherOnUpdate;
// HookDispatcherOnMount 与 HookDispatcherOnUpdate 是两个存储hook函数的对象
const HooksDispatcherOnMount: Dispatcher = { // 首次渲染时,要访问的对象
// ...省略了一些hook
useState: mountState // 首次渲染时,调用mountState函数
};
const HooksDispatcherOnUpdate: Dispatcher = { // 更新渲染时,要访问的对象
// ...省略了一些hook
useState: updateState // 渲染更新时,调用updateState函数
};
组件的首次渲染
当组件首次渲染时,调用useState
,其实调用的是一个叫做mountState
的函数,该函数通过调用mountWorkInProgress
创建一个hook对象;源码如下
js
function mountState<S>(
initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
// ...省略代码
}
function mountWorkInProgressHook(): Hook {
// 新建一个hook对象,它的结构如下:
const hook: Hook = {
memoizedState: null, // 可能保存的值有:state状态值,effect中的副作用操作;useMemo中缓存的值;useRef缓存的值
baseState: null, // 更新中,产生的最新的state值【计算最新状态值需要参考的值】
baseQueue: null, // 保存最新的更新队列
queue: null, // 存储该hook本次更新阶段的所有操作【保存待更新队列pendingQueue、更新函数dispatch】
next: null, // 链接下一个hook
};
// ...省略代码
// 返回新创建的hook对象
return workInProgressHook;
}
当我们执行第一个hookconst [count, setCount] = useState(0);
时,会创建第一个hook对象,如下图所示:

接着,我们执行const [name, setName] = useState('wxm');
,这是该hook的首次创建,依然需要创建一个新的hook对象;但workInProgressHook对象不为空,所以我们执行的是mountWorkInProgressHook 的else逻辑:workInProgressHook = workInProgressHook.next = hook;
当前App组件首次执行完成后,React会创建一个完整的hooks链表,其结构如下图:

由上图可以看出,多个hook调用形成了一个链表,该链表是一个单向链表;
组件的更新渲染
点击按钮,在事件处理函数中调用了多个更新函数,React不会立即更新状态值,而是将一次渲染添加到队列中,我们调用了三次setCount
函数,它会形成一个环形链表,结构如下:

在调用hook的setter函数时,实际上调用的是dispatchSetState
函数;
js
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A
): void {
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane, action,
hasEagerState: false,
eagerState: null,
next: (null: any)
};
if (isRenderPhaseUpdate(fiber)) {
// 如果是渲染阶段的更新:将渲染阶段的更新添加到更新队列中那个
enqueueRenderPhaseUpdate(queue, update);
} else {
// 非渲染阶段:获取最新state,执行更新
const alternate = fiber.alternate; // 用于连接current和workInProgressTree
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 表示在进入渲染阶段之前,可以先计算下一个state;
// 如果新的state和当前state相同,可以退出渲染
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
// 获取上一次更新的state
const currentState: S = (queue.lastRenderedState: any);
// eagerState:紧迫的状态
// 意义在于:在当前组件不存在更新的情况下,可以在组件render钱就提前计算出最新状态
// 当前组件不存在更新:指的是current fiber和 WIP fiber都不存在更新
const eagerState = lastRenderedReducer(currentState, action);
// 在update对象上,隐藏计算的状态,并且使用reducer去计算新的状态
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update); return;
}
} catch (error) { } finally { }
}
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
}
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
由于在事件处理函数中调用了状态更新函数,会触发组件的重新渲染,当组件再次渲染时,调用const [count, setCount] = useState(0)
,由于是第二次渲染,在调用useState
时,实际上调用的是updateState
函数。
js
function updateState<S> (
initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
[小结]
到这里,我们会发现:
第一次渲染组件,执行到useState
时,React实际上会调用mountState
去初始化状态;
当整个组件渲染完成,点击按钮执行事件处理函数时,调用setCount
函数,实际上调用dispatchSetState
将此次更新添加到队列中,并触发组件的重新渲染;
第二次渲染组件时,遇到useState
时,React实际上会执行updateState
函数;
三、源码
useState函数定义
js
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: any): Dispatcher);
}
// ReactCurrentDispatcher.current的赋值
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HookDispatcherOnMount
: HookDispatcherOnUpdate;
// HookDispatcherOnMount 与 HookDispatcherOnUpdate 是两个存储hook函数的对象
const HooksDispatcherOnMount: Dispatcher = { // 首次渲染时,要访问的对象
// ...省略了一些hook
useState: mountState // 首次渲染时,调用mountState函数
};
const HooksDispatcherOnUpdate: Dispatcher = { // 更新渲染时,要访问的对象
// ...省略了一些hook
useState: updateState // 渲染更新时,调用updateState函数
};
从useState
函数的定义可以看到,该函数接收一个参数,该参数可以是函数类型,也可以是其他类型。针对该参数的处理我们可以看函数的具体实现;
该函数返回一个数组,数组中包含两个值:一个状态对象,一个更新状态的函数;
mountState实现
js
function mountState<S>(
initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook(); // 初始化一个hook对象
if (typeof initialState === 'function') {
// 如果initialState是一个函数,获取函数返回值并重新赋值给initialState作为初始状态
initialState = initialState();
}
// memoizedState:用于存储和管理hooks状态的数据结构;表示该节点对应的状态值
// React在组件首次渲染时,创建一个memoizedState链表,包含组件的各个状态
// 缓存状态及更新状态时要参照的状态都设置为我们的初始化状态initialState
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null, // 待更新的状态
lanes: NoLanes, // 更新有优先级,默认没有优先级,或者说优先级最低
dispatch: null, // 负责更新函数
lastRenderedReducer: basicStateReducer, // 负责获取到最新的state
lastRenderedState: (initialState: any), // 上一次渲染的state,首次渲染时指定为当前state
};
hook.queue = queue; // 负责更新的函数
const dispatch: Dispatch<BasicStateAction<S>> = (queue.dispath =
(dispatchSetState.bind(null, currentlyRenderingFiber, queue): any)
);
return [hook.memoizedState, dispatch];
}
从mountState
函数的实现可以得出结论:如果传入的初始状态是个函数,则调用该函数设置初始化状态;否则,直接使用传入的参数作为初始化状态;
状态更新函数实际上调用的是dispatchSetState
函数;
dispatchSetState函数实现
js
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A
): void {
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane, action,
hasEagerState: false,
eagerState: null,
next: (null: any)
};
if (isRenderPhaseUpdate(fiber)) {
// 如果是渲染阶段的更新:将渲染阶段的更新添加到更新队列中那个
enqueueRenderPhaseUpdate(queue, update);
} else {
// 非渲染阶段:获取最新state,执行更新
const alternate = fiber.alternate; // 用于连接current和workInProgressTree
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 表示在进入渲染阶段之前,可以先计算下一个state;
// 如果新的state和当前state相同,可以退出渲染
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
// 获取上一次更新的state
const currentState: S = (queue.lastRenderedState: any);
// eagerState:紧迫的状态
// 意义在于:在当前组件不存在更新的情况下,可以在组件render钱就提前计算出最新状态
// 当前组件不存在更新:指的是current fiber和 WIP fiber都不存在更新
const eagerState = lastRenderedReducer(currentState, action);
// 在update对象上,隐藏计算的状态,并且使用reducer去计算新的状态
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update); return;
}
} catch (error) { } finally { }
}
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
}
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
updateState函数实现
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
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
if (queue === null) {
// 更新时,如果没有更新队列,会报错
throw new Error('Should have a queue. This is likely a bug in React. Please file an issue.');
}
queue.lastRenderedReducer = reducer; // reducer指向basicStateReducer
const current: Hook = (currentHook: any);
let baseQueue = current.baseQueue; // 获取上次收集到的还未执行的待更新队列
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
// 有新的更新对象,需要将添加到basequeue中
if(baseQueue !== null) {
// 合并更新队列:建立起链表
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null; // 新的更新已经添加到更新队列中,将pending置空
}
if (baseQueue !== null) {
// 有一个更新队列待运行
cosnt first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast: Update<S, A> | null = null;
let update = first;
do {
const updateLane = removeLanes(update.lane, OffscreenLane);
const isHiddenUpdate = updateLane !== update.lane;
const shouldSkipUpdate = isHiddenUpdate
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
: !isSubsetOfLanes(renderLanes, updateLane);
if (shouldSkipUpdate) {
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any)
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane
);
markSkippedUpdateLanes(updateLane);
} else {
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
lane: NoLane,
action: update.action,
hasEagerState: update.hasEgaerState,
eagerState: update.eagerState,
next: (null: any)
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
const action = update.action;
if (update.hasEagerState) {
newState = ((update.eagerState: any): S);
} else {
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
if (baseQueue === null) {
queue.lanes = NoLanes;
}
// 这里的queue.dispatch指向的依然是dispatchSetState函数;在首次挂载时就已经指定好了更新函数
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
}