React Hooks

React Hooks

在 React 内 hooks 是这样定义的,以 useState 为例

1
2
3
4
5
6
export function useState<S>(
initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}

其他几个 hooks 形式都差不多,都是调用 resolveDispatcher 返回一个对象,然后调用对象上的具体方法.

resolveDispatcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function resolveDispatcher() {
// ReactCurrentDispatcher 是一个 export 出来的对象,current 会在合适的时候进行赋值
const dispatcher = ReactCurrentDispatcher.current;

// 下面这个报错应该都见过
invariant(
dispatcher !== null,
"Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for" +
" one of the following reasons:\n" +
"1. You might have mismatching versions of React and the renderer (such as React DOM)\n" +
"2. You might be breaking the Rules of Hooks\n" +
"3. You might have more than one copy of React in the same app\n" +
"See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem."
);
return dispatcher;
}

通过翻阅源码可以发现对 ReactCurrentDispatcher.current 赋值的函数位于 packages/react-reconciler/src/ReactFiberHooks.old.js 的 renderWithHooks 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderExpirationTime: ExpirationTime
): any {
//...
if (__DEV__) {
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
// This dispatcher handles an edge case where a component is updating,
// but no stateful hooks have been used.
// We want to match the production code behavior (which will use HooksDispatcherOnMount),
// but with the extra DEV validation to ensure hooks ordering hasn't changed.
// This dispatcher does that.
ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
} else {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}

//...
// 还原原有的 Dispatcher , 原有的每个都是警告函数
// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrancy.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
}

可以用上面看到, Hooks 分为 Mount 和 Update

mountState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
/**
构造了
hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
}
对象,赋值给当前的 Fiber.memoizedState , hook 也是个链表形式,如果有多个 Hook 那么 next 指向下一个
*/
const hook = mountWorkInProgressHook();
// hooks 可以使用函数进行初始化
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
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));
// 返回数组,第一项是缓存在 Fiber 上的 hook对象, 第二项是这个 dispatchAction 经过 bind 的返回值
return [hook.memoizedState, dispatch];
}

basicStateReducer

修改 state 的值有两种方式,要么传入一个新值,要么传入一个函数,函数接收当前的 state 并返回一个新的

1
2
3
4
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === "function" ? action(state) : action;
}

dispatchAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 由于函数被返回之前已经通过bind添加了两个参数,所以用户传递的参数是第三个 action

function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
// 开发下第四个参数报错,因为 hooks 没有回调.
if (__DEV__) {
if (typeof arguments[3] === 'function') {
console.error(
"State updates from the useState() and useReducer() Hooks don't support the " +
'second callback argument. To execute a side effect after ' +
'rendering, declare it in the component body with useEffect().',
);
}
}

const currentTime = requestCurrentTimeForUpdate();
const suspenseConfig = requestCurrentSuspenseConfig();
const expirationTime = computeExpirationForFiber(
currentTime,
fiber,
suspenseConfig,
);

// update 对象
const update: Update<S, A> = {
expirationTime,
suspenseConfig,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};

if (__DEV__) {
update.priority = getCurrentPriorityLevel();
}

// Append the update to the end of the list.
// 把 update 对象添添加到链表
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;

const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
update.expirationTime = renderExpirationTime;
} else {
if (
fiber.expirationTime === NoWork &&
(alternate === null || alternate.expirationTime === NoWork)
) {
// The queue is currently empty, which means we can eagerly compute the
// next state before entering the render phase. If the new state is the
// same as the current state, we may be able to bail out entirely.
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
if (__DEV__) {
prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
// 当前的旧值
const currentState: S = (queue.lastRenderedState: any);
// 新值
const eagerState = lastRenderedReducer(currentState, action);
// Stash the eagerly computed state, and the reducer used to compute
// it, on the update object. If the reducer hasn't changed by the
// time we enter the render phase, then the eager state can be used
// without calling the reducer again.
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// Fast path. We can bail out without scheduling React to re-render.
// It's still possible that we'll need to rebase this update later,
// if the component re-renders for a different reason and by that
// time the reducer has changed.
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {
if (__DEV__) {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
}
}

scheduleUpdateOnFiber(fiber, expirationTime);
}

}

这样就准备了 update 对象添加到了 Fiber 对象上,然后安排更新, 剩下的更新部分就和 class 的 this.setStae 一样了,等待调度,然后给更新

updateState

上面是初始化部分, 接下来更新部分的 updateState

1
2
3
4
5
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}

代码只有一行.

useReducer

mountReducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 从 Fiber 对象上读取 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;

const current: Hook = (currentHook: any);

// The last rebase update that is NOT part of the base state.
// 部署于基准状态的最后一个更新
let baseQueue = current.baseQueue;

// The last pending update that hasn't been processed yet.
// 尚未处理的最后一个更新
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
// We have new updates that haven't been processed yet.
// We'll add them to the base queue.
// 我们有新的未处理的 更新
// 我们将它添加到 队列里面
if (baseQueue !== null) {
// Merge the pending queue and the base queue.
// 合并 等待更新队列和基础队列
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
if (__DEV__) {
if (current.baseQueue !== baseQueue) {
// Internal invariant that should never happen, but feasibly could in
// the future if we implement resuming, or some form of that.
console.error(
'Internal error: Expected work-in-progress queue to be a clone. ' +
'This is a bug in React.',
);
}
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}

if (baseQueue !== null) {
// We have a queue to process.
const first = baseQueue.next;
let newState = current.baseState;

let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime < renderExpirationTime) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<S, A> = {
expirationTime: update.expirationTime,
suspenseConfig: update.suspenseConfig,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Update the remaining priority in the queue.
if (updateExpirationTime > currentlyRenderingFiber.expirationTime) {
currentlyRenderingFiber.expirationTime = updateExpirationTime;
markUnprocessedUpdateTime(updateExpirationTime);
}
} else {
// This update does have sufficient priority.

if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
suspenseConfig: update.suspenseConfig,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}

// Mark the event time of this update as relevant to this render pass.
// TODO: This should ideally use the true event time of this update rather than
// its priority which is a derived and not reverseable value.
// TODO: We should skip this update if it was already committed but currently
// we have no way of detecting the difference between a committed and suspended
// update here.
markRenderEventTimeAndConfig(
updateExpirationTime,
update.suspenseConfig,
);

// Process this update.
if (update.eagerReducer === reducer) {
// If this update was processed eagerly, and its reducer matches the
// current reducer, we can use the eagerly computed state.
newState = ((update.eagerState: any): S);
} else {
const action = update.action;
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);

if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = (newBaseQueueFirst: any);
}

// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}

hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;

queue.lastRenderedState = newState;
}

const dispatch: Dispatch<A> = (queue.dispatch: any);
// 这里的 dispatch 还是上面那个mountState 里面的
return [hook.memoizedState, dispatch];
}

useReducer

mountReducer

useState 基本上就是 useReducer 的一个套壳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = mountWorkInProgressHook();
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: reducer, // useState 的 reducer 是一个固定函数, 而useReducer 是一个用户自定义的函数
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}

useEffect

mountEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null
): void {
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ("undefined" !== typeof jest) {
warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
}
}
return mountEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps
);
}
// 标记当前Fiber对象的 effecTag ,然后添加到 hookmemoizedState
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.effectTag |= fiberEffectTag;
hook.memoizedState = pushEffect(
HookHasEffect | hookEffectTag,
create,
undefined,
nextDeps
);
}

updateEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null
): void {
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ("undefined" !== typeof jest) {
warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
}
}
return updateEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps
);
}

function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;

if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
// 对比依赖是否相等
if (areHookInputsEqual(nextDeps, prevDeps)) {
pushEffect(hookEffectTag, create, destroy, nextDeps);
return;
}
}
}