React Hooks 原理探究
问题
- 什么是 Hooks?
- 常见的 Hooks 有哪些,以及使用方法?
- 实现一个 useState?
- 实现一个 useEffect?
什么是 Hooks?
常见的 Hooks 有哪些,以及使用方法?
9 分钟掌握 React Hooks 正确认知open in new window
自变量、因变量与响应式更新
这个小学知识就是:自变量与因变量。对于如下等式:
2x + 1 = y
1
x 是自变量,y 的值受 x 影响,是因变量。
const [count, setCount] = useState(0)
useEffect(() => {
// 副作用部分
document.title = `You clicked ${count} times`
}, [])
// useCallback:返回一个回调函数。
const memoizedCallback = useCallback(() => {
doSomething(a, b)
}, [a, b])
// useMemo:返回一个值。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- useState 定义自变量
- useMemo useCallback 定义没有副作用的因变量
- useEffect 定义有副作用的因变量
- useReducer 是把多个 state 合并为一个,本质上是自变量。
- useContext 解决跨组件层级传递自变量
- useRef 在自变量和视图之间做标记状态,返回的 ref 对象在组件的整个生命周期内持续存在
实现一个 useState?
let memoizedState = []
let currentCursor = 0
function useState(initialState) {
memoizedState[currentCursor] = memoizedState[currentCursor] || {
state: initialState,
action: null
}
const cursor = currentCursor
currentCursor++
if (memoizedState[cursor].action) {
memoizedState[cursor].state = memoizedState[cursor].action(memoizedState[cursor].state)
memoizedState[cursor].action = null
}
return [memoizedState[cursor].state, dispatchAction.bind(null, cursor)]
}
function dispatchAction(cursor, action) {
if (memoizedState[cursor].action === null) {
memoizedState[cursor].action = action
}
schedule()
}
function App(params) {
const [count, updateCount] = useState(0)
const [count2, updateCount2] = useState(0)
return (
<div>
<button onClick={() => updateCount(e => e + 10)}>{count}</button>
<button onClick={() => updateCount2(e => e + 20)}>{count2}</button>
</div>
)
}
function schedule() {
currentCursor = 0
render()
}
schedule()
function render() {
ReactDOM.render(App(), document.getElementById("root"))
}
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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Hook Test</title>
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<style>
button {
width: 100px;
height: 38px;
font-size: 20px;
cursor: pointer;
border-radius: 5px;
border: 1px solid #ccc;
line-height: 38px;
text-align: center;
background-color: #fff;
padding: 0;
}
</style>
<body>
<div id="root"></div>
</body>
<script src="hooks.js" type="text/babel"></script>
</html>
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
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
接近 hooks 源码 90 行实现
let isMount = true
let workInProgressHook = null
const fiber = {
stateNode: App,
memoizedState: null
}
function useState(initialState) {
let hook
if (isMount) {
hook = {
memoizedState: initialState,
next: null, // 链表
queue: {
// 存储action的环形队列
pending: null
}
}
if (!fiber.memoizedState) {
fiber.memoizedState = hook
} else {
workInProgressHook.next = hook
}
workInProgressHook = hook
} else {
// console.log('~~~ workInProgressHook', workInProgressHook)
hook = workInProgressHook
workInProgressHook = workInProgressHook.next
}
// 执行 action
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)];
return [baseState, action => dispatchAction(hook.queue, action)]
}
function dispatchAction(queue, action) {
const update = {
action,
next: null
}
if (queue.pending === null) {
// u0 -> u0
update.next = update
} else {
// u0 -> u1 ; u1 -> u0
update.next = queue.pending.next
queue.pending.next = update
}
queue.pending = update
schedule()
}
// 调度器
function schedule() {
workInProgressHook = fiber.memoizedState
render()
isMount = false
}
function App(params) {
const [count, updateCount] = useState(0)
const [count2, updateCount2] = useState(1)
const [count3, updateCount3] = useState(2)
console.log("isMount", isMount)
console.log("count", count)
console.log("count2", count2)
return (
<div>
<button onClick={() => updateCount(e => e + 10)}>{count}</button>
<button onClick={() => updateCount2(e => e + 20)}>{count2}</button>
</div>
)
}
schedule()
function render() {
ReactDOM.render(fiber.stateNode(), document.getElementById("root"))
}
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
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
小型 render
const atom = (function () {
const createElement = (tag, attrs, ...childs) => {
return { tag, attrs, childs }
}
// 将 vdom 转换为真实 dom
const render = (vdom, root) => {
if (typeof vdom === "string" || typeof vdom === "number") {
// 子元素如果是字符串,直接拼接字符串
root.innerText += vdom
return
}
const dom = document.createElement(vdom.tag)
if (vdom.attrs) {
for (let attr in vdom.attrs) {
const value = vdom.attrs[attr]
setAttribute(dom, attr, value)
}
}
// 遍历子节点
vdom.childs && vdom.childs.forEach(child => render(child, dom))
// 将子元素挂载到其真实 DOM 的父元素上
root.appendChild(dom)
}
// 设置 dom 节点属性
const setAttribute = (dom, attr, value) => {
if (attr === "className") {
attr = "class"
}
// 处理事件
if (/on\w+/.test(attr)) {
attr = attr.toLowerCase()
dom[attr] = value || ""
} else if (attr === "style" && value) {
// 处理 style 样式,可以是个字符串或者对象
if (typeof value === "string") {
dom.style.cssText = value
} else if (typeof value === "object") {
for (let styleName in value) {
dom.style[styleName] =
typeof value[styleName] === "number" ? value[styleName] + "px" : value[styleName]
}
}
} else {
// 其他属性
dom.setAttribute(attr, value)
}
}
const React = {
createElement
}
const ReactDOM = {
render: (vdom, root) => {
root.innerText = ""
render(vdom, root)
}
}
return {
React,
ReactDOM
}
})()
const { React, ReactDOM } = atom
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
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