react中hooks实现原理

以客户端 useState 为例

  1. 声明 useState,通过 ReactCurrentDispatcher.current 来实现
  2. ReactCurrentDispatcher.current 的赋值是在 packages/react-reconciler/src/ReactFiberHooks 文件中区分挂载、更新和重新渲染分别实现的

挂载

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
// packages/react-reconciler/src/ReactFiberHooks.new.js
function mountState(initialState) {
const hook = mountWorkInProgressHook()
if (typeof initialState === 'function') {
initialState = initialState()
}
hook.memoizedState = hook.baseState = initialState
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
})
const dispatch = (queue.dispatch = dispatchAction.bind(
null,
currentlyRenderingFiber,
queue
))
return [hook.memoizedState, dispatch]
}

function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
}

if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook
}
return workInProgressHook
}

更新

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
// file: packages/react-reconciler/src/ReactFiberHooks.new.js
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState)
}

function updateReducer(reducer, initialArg, init) {
const hook = updateWorkInProgressHook()
const queue = hook.queue

queue.lastRenderedReducer = reducer

const current = currentHook

// 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
}
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 updateLane = update.lane
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 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 = {
lane: updateLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: null,
}
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone
newBaseState = newState
} else {
newBaseQueueLast = newBaseQueueLast.next = clone
}
// Update the remaining priority in the queue.
// TODO: Don't need to accumulate this. Instead, we can remove
// renderLanes from the original lanes.
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane
)
markSkippedUpdateLanes(updateLane)
} else {
// This update does have sufficient priority.

if (newBaseQueueLast !== null) {
const clone = {
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: null,
}
newBaseQueueLast = newBaseQueueLast.next = clone
}

// 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
} 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
}

// 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
}

// Interleaved updates are stored on a separate queue. We aren't going to
// process them during this render, but we do need to track which lanes
// are remaining.
const lastInterleaved = queue.interleaved
if (lastInterleaved !== null) {
let interleaved = lastInterleaved
do {
const interleavedLane = interleaved.lane
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
interleavedLane
)
markSkippedUpdateLanes(interleavedLane)
interleaved = interleaved.next
} while (interleaved !== lastInterleaved)
} else if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.lanes = NoLanes
}

const dispatch = (queue.dispatch: any)
return [hook.memoizedState, dispatch]
}

重渲染

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
function dispatchAction(fiber, queue, action) {
const eventTime = requestEventTime()
const lane = requestUpdateLane(fiber)

const update = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: null,
}

const alternate = fiber.alternate
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
didScheduleRenderPhaseUpdateDuringThisPass =
didScheduleRenderPhaseUpdate = true
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
} else {
// 其它情况更新
}
}

basicStateReducer

1
2
3
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action
}

所有的 hooks 都是挂载在 ReactCurrentDispatcher 对象上的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Test() {
const [a, setA] = useState()
return <div>{a}</div>
}

ReactDOM.render(
<Test />,
document.getElementById('root') || document.getElementsByTagName('body')[0]
)

// 编译之后
function Test() {
var _useState = (0, _react.useState)(0), // 等价于 _useState = _react.useState(0)
_useState2 = _slicedToArray(_useState, 2),
a = _useState2[0],
setA = _useState2[1]

return _react2.default.createElement('div', null, a)
}

_reactDom2.default.render(
_react2.default.createElement(Test, null),
document.getElementById('root') || document.getElementsByTagName('body')[0]
)

编译后实际是把函数组件当作参数传递下去了

参考