Hooks

# Hooks 理念
React 的LOGO图案代表原子的符号。世间万物由原子组成,原子的类型与属性决定了事物的外观与表现。
在React中,可将UI拆分为很多独立的单元,每个单元叫Component。Component的属性与类型决定了UI的外观与表现。
然而,原子中还有更小的粒子--电子(electron)。电子可解释原子是如何工作的。
在React中,ClassComponent是一类原子,Hooks是贴近事物运行规律的电子。
React的架构遵循schedule - render - commit的运行流程。
ClassComponent作为React世界的原子,他的生命周期(componentWillXX/componentDidXX)是为了介入React的运行流程而实现的更上层抽象。
Hooks则更贴近React内部运行的各种概念(state|context|life-cycle)。
作为React技术栈的开发者,初次学习Hooks时,会拿ClassComponent的生命周期来类比Hooks API的执行时机。
但当我们熟练使用Hooks时发现,这两者的概念有很多割裂感,并不是同一抽象层次可互相替代的概念。
如:替代componentWillReceiveProps的Hooks是什么呢?
有的说是useEffect
useEffect( () => {
console.log('something updated');
}, [props.something])
2
3
但是componentWillReceiveProps是在render阶段执行,而useEffect是在commit阶段完成渲染后异步执行。
Concurrent Mode是React未来的发展方向,而Hooks是能够最大限度发挥Concurrent Mode潜力的Component构建方式。
# 极简Hooks实现
为更好理解Hooks原理,遵循React的运行流程,实现一个极简useState Hook
完整代码如下:
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.next)
hook.queue.pending = null;
}
hook.memoizedState = baseState;
return [baseState, dispatchAction.bind(null, hook.queue)];
}
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
# 工作原理
对于useState Hook,考虑如下例子:
function App() {
const [num, updateNum] = useState(0);
return <p onClick={() => updateNum(num => num + 1)}>{num}</p>;
}
2
3
4
5
可将工作分两部分:
- 通过一些途径产生更新,更新会造成组件render
- 组件render时
useState返回的num为更新后的结果 其中,步骤1的更新可分mount和update - 调用
ReactDOM.render会产生mount的更新,更新内容为useState的initialValue(即0) - 点击p标签触发
updateNum会产生一次update的更新,更新内容num=>num+1
# 更新是什么
在我们的极简例子中,更新是如下数据结构:
const update = {
// 更新执行的函数
action,
// 与同一个Hook的其他更新形成链表
next: null
}
2
3
4
5
6
对应App来说,点击p标签产生的update的action为num => num + 1
若改写App的onClick:
// 之前
return <p onClick={() => updateNum(num => num + 1)}>{num}</p>;
// 之后
return <p onClick={() => {
updateNum(num => num + 1);
updateNum(num => num + 1);
updateNum(num => num + 1);
}}>{num}</p>;
2
3
4
5
6
7
8
9
那么点击p标签会产生三个update
# update数据结构
这些update是如何组合在一起呢?
答案是 他们会形成环状单向链表。
调用updateNum实际调用的是dispatchAction.bind(null, hook.queue)
function dispatchAction(queue, action) {
// 创建update
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;
// 模拟React开始调度更新
schedule();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# useEffect
在flushPassiveEffects方法内部会从全局变量rootWithPendingPassiveEffects获取effectList
# flushPassiveEffectsImpl
flushPassiveEffects (opens new window)内部会设置优先级,并执行flushPassiveEffectsImpl。
flushPassiveEffectsImpl主要做三件事:
- 调用该
useEffect在上一次render时的销毁函数 - 调用该
useEffect在本次render时的回调函数 - 如果存在同步任务,不需要等待下次事件循环的宏任务,提前执行他
# 阶段一:销毁函数的执行
# useRef
# useMemo与useCallback
# mount
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
// 创建并返回当前hook
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 计算value
const nextValue = nextCreate();
// 将value与deps保存在hook.memoizedState
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
// 创建并返回当前hook
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 将value与deps保存在hook.memoizedState
hook.memoizedState = [callback, nextDeps];
return callback;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
可看到,与mountCallback这两个唯一的区别是
mountMemo会将回调函数(nextCreate)的执行结果作为value保存mountCallback会将回调函数作为value保存
# update
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
// 返回当前hook
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 判断update前后value是否变化
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 未变化
return prevState[0];
}
}
}
// 变化,重新计算value
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
// 返回当前hook
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 判断update前后value是否变化
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 未变化
return prevState[0];
}
}
}
// 变化,将新的callback作为value
hook.memoizedState = [callback, nextDeps];
return callback;
}
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
可见,对于update,这两个hook的唯一区别也是回调函数本身还是回调函数的执行结果作为value。