js执行流程

js 引擎

浏览器 JavaScript 引擎
chrome V8
safari JavaScriptCore
Firefox SpiderMonkey
Edge Chakra

js 执行过程

  1. 对源码进行词法分析
  2. 进行语法分析
  3. 生成抽象语法树
  4. 生成可执行代码(可能有优化过程,此处代码可能是字节码或者机器码)
  5. 执行

词法分析

将程序源代码分解成对编程语言来说有意义的代码块,这些代码块被称为词法单元(token)。

语法分析

根据生成的 Token 进行语法分析。

V8 引擎

主要模块

Parser

负责将 JavaScript 源码转换为 Abstract Syntax Tree (AST)。在 V8 中有两个解析器用于解析 JavaScript 代码,分别是 ParserPre-Parser

  • Parser 解析器又称为 full parser(全量解析) 或者 eager parser(饥饿解析)。它会解析所有立即执行的代码,包括语法检查,生成 AST,以及确定词法作用域。
  • Pre-Parser 又称为惰性解析,它只解析未被立即执行的代码(如函数),不生成 AST ,只确定作用域,以此来提高性能。当预解析后的代码开始执行时,才进行 Parser 解析。
Ignition

interpreter,即解释器,负责将 AST 转换为 Bytecode,解释执行 Bytecode;同时收集 TurboFan 优化编译所需的信息,比如函数参数的类型

TurboFan

compiler,即编译器,利用 Ignitio 所收集的类型信息,将 Bytecode 转换为优化的机器代码

Orinoco

garbage collector,垃圾回收模块,负责将程序不再需要的内存空间回收;

执行过程

  1. 扫描所有的源代码,进行词法分析,生成 Tokens
  2. Parser 解析器根据 Tokens 生成 AST,存在预编译和编译
  3. Ignition 解释器将 AST 转换为字节码,并解释执行
  4. TurboFan 编译器负责将热点函数优化编译为机器指令执行

执行过程

优化及优化导致的问题修复

Ignition 开始执行 JavaScript 代码后,V8 会一直观察 JavaScript 代码的执行情况,并记录执行信息,如每个函数的执行次数、每次调用函数时,传递的参数类型等。

如果一个函数被调用的次数超过了内设的阈值,监视器就会将当前函数标记为热点函数(Hot Function),并将该函数的字节码以及执行的相关信息发送给 TurboFanTurboFan 会根据执行信息做出一些进一步优化此代码的假设,在假设的基础上将字节码编译为优化的机器代码。如果假设成立,那么当下一次调用该函数时,就会执行优化编译后的机器代码,以提高代码的执行性能;如果假设不成立,不知道你们有没有注意到上图中有一条由 optimized code 指向 bytecode 的红色指向线。此过程叫做 deoptimize(优化回退),将优化编译后的机器代码还原为字节码。

优化过程

参考

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]
)

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

参考

react从dom.render到初次组件渲染完成

dom.render 到初次组件渲染完成

  1. render(element,container,callback) 调用 legacyRenderSubtreeIntoContainer(null,element,container,false,callback)
  2. legacyRenderSubtreeIntoContainer(parentComponent,children,container,forceHydrate,callback) 调用legacyCreateRootFromDOMContainer(container,false) 创建容器并赋值给 container._reactRootContainer
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
// file: packages/react-dom/src/client/ReactDOMLegacy.js
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function
) {
let root = container._reactRootContainer
let fiberRoot: FiberRoot
if (!root) {
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate
)
fiberRoot = root._internalRoot
if (typeof callback === 'function') {
const originalCallback = callback
callback = function () {
const instance = getPublicRootInstance(fiberRoot)
originalCallback.call(instance)
}
}
// 初始化同步更新
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback)
})
} else {
// 更新
}
return getPublicRootInstance(fiberRoot)
}
  1. 通过 unbatchedUpdates 同步更新容器元素并调用回调函数

构建容器

  1. legacyCreateRootFromDOMContainer 调用 createLegacyRoot(container) 构建容器
  2. 调用 new ReactDOMLegacyRoot(container) 构建实例
1
2
3
4
5
6
7
// file: packages/react-dom/src/client/ReactDOMRoot.js
export function createLegacyRoot(
container: Container,
options?: RootOptions
): RootType {
return new ReactDOMLegacyRoot(container, options)
}
  1. 构造函数中调用 createRootImpl(container, ConcurrentRoot) 创建根容器并赋值给 _internalRoot 属性
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
// file: packages/react-dom/src/client/ReactDOMRoot.js
function ReactDOMLegacyRoot(container: Container, options: void | RootOptions) {
this._internalRoot = createRootImpl(container, LegacyRoot, options)
}

ReactDOMLegacyRoot.prototype.render = function (children: ReactNodeList): void {
const root = this._internalRoot
updateContainer(children, root, null, null)
}

ReactDOMLegacyRoot.prototype.unmount = function (): void {
const root = this._internalRoot
const container = root.containerInfo
updateContainer(null, root, null, () => {
unmarkContainerAsRoot(container)
})
}

function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions
) {
const root = createContainer(container, tag)
markContainerAsRoot(root.current, container)
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container
listenToAllSupportedEvents(rootContainerElement)

return root
}
  1. 调用 createContainer(container, ConcurrentRoot) 构建根元素
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
148
149
150
151
152
153
154
155
156
// file: packages/react-reconciler/src/ReactFiberRoot.old.js
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean
): OpaqueRoot {
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride
)
}

// 1. 调用 `new FiberRootNode(container, ConcurrentRoot)` 构建根元素
// 2. 调用 `createHostRootFiber` 构建 `Fiber`
// 3. 初始化更新队列
// file: packages/react-reconciler/src/ReactFiberRoot.old.js
function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean
): FiberRoot {
const root: FiberRoot = new FiberRootNode(containerInfo, tag, hydrate)
const uninitializedFiber = createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride
)
root.current = uninitializedFiber
uninitializedFiber.stateNode = root
const initialState = { element: null }
uninitializedFiber.memoizedState = initialState
initializeUpdateQueue(uninitializedFiber)

return root
}

function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag
this.containerInfo = containerInfo
this.pendingChildren = null
this.current = null
this.pingCache = null
this.finishedWork = null
this.timeoutHandle = noTimeout
this.context = null
this.pendingContext = null
this.hydrate = hydrate
this.callbackNode = null
this.callbackPriority = NoLane
this.eventTimes = createLaneMap(NoLanes)
this.expirationTimes = createLaneMap(NoTimestamp)

this.pendingLanes = NoLanes
this.suspendedLanes = NoLanes
this.pingedLanes = NoLanes
this.expiredLanes = NoLanes
this.mutableReadLanes = NoLanes
this.finishedLanes = NoLanes

this.entangledLanes = NoLanes
this.entanglements = createLaneMap(NoLanes)
}

// file: packages/react-reconciler/src/ReactFiber.old.js
function createHostRootFiber(
tag: RootTag,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean
): Fiber {
let mode
if (tag === ConcurrentRoot) {
mode = ConcurrentMode
if (isStrictMode === true) {
mode |= StrictLegacyMode

if (enableStrictEffects) {
mode |= StrictEffectsMode
}
} else if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode |= StrictLegacyMode | StrictEffectsMode
}
if (
!enableSyncDefaultUpdates ||
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
) {
mode |= ConcurrentUpdatesByDefaultMode
}
} else {
mode = NoMode
}

if (enableProfilerTimer && isDevToolsPresent) {
mode |= ProfileMode
}

return createFiber(HostRoot, null, null, mode)
}

function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode
) {
// Instance
this.tag = tag
this.key = key
this.elementType = null
this.type = null
this.stateNode = null

// Fiber
this.return = null
this.child = null
this.sibling = null
this.index = 0

this.ref = null

this.pendingProps = pendingProps
this.memoizedProps = null
this.updateQueue = null
this.memoizedState = null
this.dependencies = null

this.mode = mode

// Effects
this.flags = NoFlags
this.subtreeFlags = NoFlags
this.deletions = null

this.lanes = NoLanes
this.childLanes = NoLanes

this.alternate = null
}

function createFiber(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode
): Fiber {
return new FiberNode(tag, pendingProps, key, mode)
}
  1. 调用markContainerAsRoot(root.current, container)标记根元素
1
2
3
4
5
6
// file: packages/react-dom/src/client/ReactDOMComponentTree.js
const randomKey = Math.random().toString(36).slice(2)
const internalContainerInstanceKey = '__reactContainer$' + randomKey
export function markContainerAsRoot(hostRoot: Fiber, node: Container) {
node[internalContainerInstanceKey] = hostRoot
}
  1. 调用listenToAllSupportedEvents(rootContainerElement) 向根元素上注册所有原生监听事件,但 selectionchange 事件注册在 document

构建的容器

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
const container =document.getElementById("root")
const root = container._reactRootContainer = {
_internalRoot: {
tag: RootTag,
containerInfo: container,
pendingChildren: null,
current: null,
pingCache: null,
finishedWork: null,
timeoutHandle: noTimeout
context: null,
pendingContext: null,
hydrate: hydrate,
callbackNode: null,
callbackPriority: NoLane,
eventTimes: [NoLanes],
expirationTimes: [NoTimestamp],
pendingLanes: NoLanes,
suspendedLanes: NoLanes,
pingedLanes: NoLanes,
expiredLanes: NoLanes,
mutableReadLanes: NoLanes,
finishedLanes: NoLanes,
entangledLanes: NoLanes,
entanglements: [NoLanes],
},
current: {
tag: tag,
key: key,
elementType: null,
type: null,
stateNode: root,

// Fiber
return: null,
child: null,
sibling: null,
index: 0,

ref: null,

pendingProps: pendingProps,
memoizedProps: null,
updateQueue: null,
memoizedState: { element: null },
dependencies: null,

mode: NoMode,

// Effects
flags: NoFlags,
subtreeFlags: NoFlags,
deletions: null,

lanes: NoLanes,
childLanes: NoLanes,

alternate: null
}
}
const fiberRoot = root._internalRoot
container['__reactContainer$' + randomKey] = root.current

更新容器

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
// file: packages/react-reconciler/src/ReactFiberReconciler.old.js
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function
): Lane {
const current = container.current
const eventTime = requestEventTime() // 获取更新时间
const lane = requestUpdateLane(current) // 获取更新任务的优先级

const context = getContextForSubtree(parentComponent) // 获取 context
if (container.context === null) {
container.context = context
} else {
container.pendingContext = context
}

const update = createUpdate(eventTime, lane) // 构建一次更新
update.payload = { element }

callback = callback === undefined ? null : callback
if (callback !== null) {
update.callback = callback
}

enqueueUpdate(current, update, lane)
const root = scheduleUpdateOnFiber(current, lane, eventTime)
if (root !== null) {
entangleTransitions(root, current, lane)
}

return lane
}
  1. 获取更新优先级,位数越底,优先级越高
  2. 获取 context
  3. 通过 createUpdate 创建更新
1
2
3
4
5
6
7
8
9
10
11
12
// file: packages/react-reconciler/src/ReactUpdateQueue.old.js
export function createUpdate(eventTime: number, lane: Lane) {
const update = {
eventTime,
lane,
tag: UpdateState,
payload: null,
callback: null,
next: null,
}
return update
}
  1. 通过 enqueueUpdate 把更新放入更新队列
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
// file: packages/react-reconciler/src/ReactUpdateQueue.old.js
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane
) {
const updateQueue = fiber.updateQueue
if (updateQueue === null) {
// 卸载时执行
return
}
const sharedQueue = updateQueue.shared
// 正在更新
if (isInterleavedUpdate(fiber, lane)) {
const interleaved = sharedQueue.interleaved
if (interleaved === null) {
// 构建更新环,并放入更新队列
update.next = update
pushInterleavedQueue(sharedQueue)
} else {
update.next = interleaved.next
interleaved.next = update
}
sharedQueue.interleaved = update
} else {
const pending = sharedQueue.pending
if (pending === null) {
// 构建更新环,并放入更新队列
update.next = update
} else {
update.next = pending.next
pending.next = update
}
sharedQueue.pending = update
}
}
  1. 通过 scheduleUpdateOnFiber 调度更新

webpack源码阅读(二)

webpack Compilation 执行过程

  1. 构建 Compilation 实例
  2. 调用 compilation.finish 执行构建
  3. 异步调用 finishModules 钩子函数
  4. 调用 compilation.seal 完成构建
  5. 构建 ChunkGraph 实例,并调用 ChunkGraph.setChunkGraphForModule(module, chunkGraph)
  6. 同步调用 seal 钩子函数
  7. 同步调用 optimizeDependencies 钩子函数,等待钩子函数执行完毕
  8. 同步调用 afterOptimizeDependencies 钩子函数
  9. 同步调用 beforeChunks 钩子函数
  10. 构建 chunk
  11. 同步调用 afterChunks 钩子函数
  12. 同步调用 optimize 钩子函数
  13. 同步调用 optimizeModules 钩子函数,等待钩子函数执行完毕
  14. 同步调用 afterOptimizeModules 钩子函数
  15. 同步调用 optimizeChunks 钩子函数,等待钩子函数执行完毕
  16. 同步调用 afterOptimizeChunks 钩子函数
  17. 异步调用 optimizeTree 钩子函数
  18. 同步调用 afterOptimizeTree 钩子函数
  19. 异步调用 optimizeChunkModules 钩子函数
  20. 同步调用 afterOptimizeChunkModules 钩子函数
  21. 同步调用 shouldRecord 钩子函数
  22. 同步调用 reviveModules 钩子函数
  23. 同步调用 beforeModuleIds 钩子函数
  24. 同步调用 moduleIds 钩子函数
  25. 同步调用 optimizeModuleIds 钩子函数
  26. 同步调用 afterOptimizeModuleIds 钩子函数
  27. 同步调用 reviveChunks 钩子函数
  28. 同步调用 beforeChunkIds 钩子函数
  29. 同步调用 chunkIds 钩子函数
  30. 同步调用 optimizeChunkIds 钩子函数
  31. 同步调用 afterOptimizeChunkIds 钩子函数
  32. shouldRecord 钩子函数为正,则同步调用 recordModules 钩子函数
  33. shouldRecord 钩子函数为正,则同步调用 recordChunks 钩子函数
  34. 同步调用 optimizeCodeGeneration 钩子函数
  35. 同步调用 beforeModuleHash 钩子函数
  36. 同步调用 afterModuleHash 钩子函数
  37. 同步调用 beforeCodeGeneration 钩子函数
  38. 调用 codeGeneration 生成代码
  39. 同步调用 afterCodeGeneration 钩子函数
  40. 同步调用 beforeRuntimeRequirements 钩子函数
  41. 提取 modules 中的 runtime,对于每个 moduleruntime 都同步调用 additionalModuleRuntimeRequirements 钩子函数
  42. 若存在对应的 runtimeRequirementInModule 钩子函数,则同步调用该钩子函数
  43. 对每个 chunk 同步调用 additionalChunkRuntimeRequirements 钩子函数
  44. 对每个 chunk 中依赖的 runtime 调用 runtimeRequirementInChunk 钩子函数
  45. 构建含有重复依赖的依赖树
  46. 每个入口文件的依赖进行扁平化处理,去掉重复依赖,然后同步调用 additionalTreeRuntimeRequirements 钩子函数
  47. 每个入口文件的依赖一次调用 runtimeRequirementInTree 钩子函数
  48. 同步调用 afterRuntimeRequirements 钩子函数
  49. 同步调用 beforeHash 钩子函数
  50. 同步调用 updateHash 钩子函数
  51. 若为非 fullHashModules 是同步调用 contentHash 钩子函数
  52. 同步调用 fullHash 钩子函数
  53. 对每个 chunk 同步调用 fullHash 钩子函数
  54. 同步调用 afterHash 钩子函数
  55. 若需要记录则同步调用 shouldRecord 钩子函数
  56. 清理 chunk 记录的文件
  57. 同步调用 beforeModuleAssets 钩子函数
  58. 针对 module 的每一个需要输出的资源同步调用 moduleAsset 钩子函数
  59. 同步调用 shouldGenerateChunkAssets 判断是否需要输出资源
  60. 输出资源前同步调用 beforeChunkAssets 钩子函数
  61. 针对每个 chunk 构建一个 manifest 文件
  62. 为每个 chunk 写入包含的资源
  63. 同步调用 chunkAsset 钩子函数
  64. 异步调用 processAssets 钩子函数
  65. 同步调用 afterProcessAssets 钩子函数
  66. 构建依赖
  67. 若需要记录则同步调用 record 钩子函数
  68. 同步调用 needAdditionalSeal 钩子函数,判读是否需要新增
  69. 异步调用 afterSeal 钩子函数
  70. 回调

Module

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
{
/** @type {string} */
type: type,
/** @type {string} */
context: context,
/** @type {boolean} */
needId: true,

// Unique Id
/** @type {number} */
debugId: debugId++,

// Info from Factory
/** @type {ResolveOptions} */
resolveOptions: EMPTY_RESOLVE_OPTIONS,
/** @type {object | undefined} */
factoryMeta: undefined,
// TODO refactor this -> options object filled from Factory
// TODO webpack 6: use an enum
/** @type {boolean} */
useSourceMap: false,
/** @type {boolean} */
useSimpleSourceMap: false,

// Info from Build
/** @type {WebpackError[] | undefined} */
_warnings: undefined,
/** @type {WebpackError[] | undefined} */
_errors: undefined,
/** @type {BuildMeta} */
buildMeta: undefined,
/** @type {object} */
buildInfo: undefined,
/** @type {Dependency[] | undefined} */
presentationalDependencies: undefined,
}

chunk 具有的属性

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
{
/** @type {number | string | null} */
id: null,
/** @type {(number|string)[] | null} */
ids: null,
/** @type {number} */
debugId: debugId++,
/** @type {string} */
name: name,
/** @type {SortableSet<string>} */
idNameHints: new SortableSet(),
/** @type {boolean} */
preventIntegration: false,
/** @type {(string | function(PathData, AssetInfo=): string)?} */
filenameTemplate: undefined,
/** @private @type {SortableSet<ChunkGroup>} */
_groups: new SortableSet(undefined, compareChunkGroupsByIndex),
/** @type {RuntimeSpec} */
runtime: undefined,
/** @type {Set<string>} */
files: new ChunkFilesSet(),
/** @type {Set<string>} */
auxiliaryFiles: new Set(),
/** @type {boolean} */
rendered: false,
/** @type {string=} */
hash: undefined,
/** @type {Record<string, string>} */
contentHash: Object.create(null),
/** @type {string=} */
renderedHash: undefined,
/** @type {string=} */
chunkReason: undefined,
/** @type {boolean} */
extraAsync: false,
}

ChunkGraph 具有的属性

1
2
3
4
5
6
7
8
9
10
11
12
{
/** @private @type {WeakMap<Module, ChunkGraphModule>} */
_modules: new WeakMap(),
/** @private @type {WeakMap<Chunk, ChunkGraphChunk>} */
_chunks: new WeakMap(),
/** @private @type {WeakMap<AsyncDependenciesBlock, ChunkGroup>} */
_blockChunkGroups: new WeakMap(),
/** @private @type {Map<string, string | number>} */
_runtimeIds: new Map(),
/** @type {ModuleGraph} */
moduleGraph: moduleGraph;
}

备注

  1. 对于一份同逻辑的代码,当我们手写下一个一个的文件,它们无论是 ESM 还是 commonJS 或是 AMD,他们都是 module
  2. 当我们写的 module 源文件传到 webpack 进行打包时,webpack 会根据文件引用关系生成 chunk 文件,webpack 会对这个 chunk 文件进行一些操作;
  3. webpack 处理好 chunk 文件后,最后会输出 bundle 文件,这个 bundle 文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行。

webpack源码阅读(一)

webpack compile 执行过程

  1. 获取默认基础参数
  2. 构建 Compiler 实例
  3. 通过插件 NodeEnvironmentPlugin 插入日志输出,构建缓存,监听文件变化,监听 NodeEnvironmentPlugin 事件
  4. 注入自定义的插件
  5. 设置默认配置
  6. 同步执行 environment 钩子函数
  7. 同步执行 afterEnvironment 钩子函数
  8. 注入预设的插件系统
  9. 同步执行 initialize 钩子函数
  10. 执行 run ,开始构建
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
// file: lib/webpack.js
const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ (
options,
callback,
) => {
const create = () => {
let compiler
let watch = false
/** @type {WatchOptions|WatchOptions[]} */
let watchOptions
if (Array.isArray(options)) {
// 多个 compiler
} else {
/** @type {Compiler} */
compiler = createCompiler(options)
// watch 配置
}
return { compiler, watch, watchOptions }
}

const { compiler, watch, watchOptions } = create()
// 执行 run ,开始构建
compiler.run((err, stats) => {
compiler.close((err2) => {
callback(err || err2, stats)
})
})
return compiler
}

const createCompiler = (rawOptions) => {
const options = getNormalizedWebpackOptions(rawOptions)
applyWebpackOptionsBaseDefaults(options) // 构建默认基础选项
const compiler = new Compiler(options.context) // 构建 compiler 实例
compiler.options = options
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging,
}).apply(compiler) // 插入日志输出,构建缓存系统,监听文件变化,监听 NodeEnvironmentPlugin 事件
// 注入插件
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === 'function') {
plugin.call(compiler, compiler)
} else {
plugin.apply(compiler)
}
}
}
applyWebpackOptionsDefaults(options) // 设置默认配置
compiler.hooks.environment.call() // 执行 environment 绑定的事件
compiler.hooks.afterEnvironment.call() // 执行 afterEnvironment 绑定的事件
new WebpackOptionsApply().process(options, compiler) // 注入预设的插件系统
compiler.hooks.initialize.call() // 执行 initialize 绑定的事件
return compiler
}

run 执行过程

  1. 异步调用 beforeRun 钩子函数
  2. 异步调用 run 钩子函数
  3. 异步读取构建记录
  4. 实例化 NormalModuleFactory 对象,同步调用 NormalModuleFactory 钩子函数
  5. 实例化 ContextModuleFactory 对象,同步调用 ContextModuleFactory 钩子函数
  6. 构建 Compilation 需要的实例参数
  7. 异步调用 beforeCompile 钩子函数
  8. 同步调用 compile 钩子函数
  9. 构建 Compilation 实例
  10. 注入 namerecords 属性
  11. 同步调用 thisCompilation 钩子函数
  12. 同步调用 compilation 钩子函数
  13. 异步调用 make 钩子函数
  14. 异步调用 finishMake 钩子函数
  15. nextTick 中依次调用 compilation.finishcompilation.seal
  16. 异步调用 afterCompile 钩子函数
  17. 同步调用 shouldEmit 钩子判断是否需要输出资源
  18. 输出资源文件,异步调用 emit 钩子函数
  19. 同步调用 needAdditionalPass 钩子函数
  20. 把构建记录 records 写入文件
  21. 异步调用 done 钩子函数
  22. 回调 callback 完成构建
  23. 同步调用 afterDone 钩子函数
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
// file: lib/Compiler.js
class Compiler {
run(callback) {
const finalCallback = (err, stats) => {
this.idle = true
this.cache.beginIdle()
this.idle = true
this.running = false
// 回调,然后调用 afterDone 钩子函数,结束构建
if (callback !== undefined) callback(err, stats)
this.hooks.afterDone.call(stats)
}

const startTime = Date.now()
this.running = true

const onCompiled = (err, compilation) => {
// 不需要输出资源直接结束此次构建
if (this.hooks.shouldEmit.call(compilation) === false) {
compilation.startTime = startTime
compilation.endTime = Date.now()
const stats = new Stats(compilation)
this.hooks.done.callAsync(stats, (err) => {
return finalCallback(null, stats)
})
return
}

process.nextTick(() => {
this.emitAssets(compilation, (err) => {
// 判断是否需要新增资源,如果需要新增则结束此次编译流程,然后重新启动一个新的编译流程
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true
compilation.startTime = startTime
compilation.endTime = Date.now()
const stats = new Stats(compilation)
this.hooks.done.callAsync(stats, (err) => {
this.hooks.additionalPass.callAsync((err) => {
this.compile(onCompiled)
})
})
return
}

// 输出构建的记录,结束编译流程
this.emitRecords((err) => {
compilation.startTime = startTime
compilation.endTime = Date.now()
const stats = new Stats(compilation)
this.hooks.done.callAsync(stats, (err) => {
this.cache.storeBuildDependencies(
compilation.buildDependencies,
(err) => {
return finalCallback(null, stats)
},
)
})
})
})
})
}

const run = () => {
this.hooks.beforeRun.callAsync(this, (err) => {
this.hooks.run.callAsync(this, (err) => {
// 读取编译记录
this.readRecords((err) => {
// 开始执行编译
this.compile(onCompiled)
})
})
})
}
run() // 开始执行编译进程
}

compile(callback) {
const params = this.newCompilationParams() // 构建 Compilation 参数
this.hooks.beforeCompile.callAsync(params, (err) => {
this.hooks.compile.call(params)
const compilation = this.newCompilation(params) // 构建 Compilation 实例,开始一次编译
this.hooks.make.callAsync(compilation, (err) => {
this.hooks.finishMake.callAsync(compilation, (err) => {
process.nextTick(() => {
// 执行 Compilation 内的钩子
compilation.finish((err) => {
compilation.seal((err) => {
// 一次编译完成
this.hooks.afterCompile.callAsync(compilation, (err) => {
return callback(null, compilation)
})
})
})
})
})
})
})
}
}

代码执行流程图

webpack compile 代码执行流程

Performance

Performance

属性

只读属性 Performance.navigation 会返回一个 PerformanceNavigation 对象。

  • type: 一个无符号短整型,表示是如何导航到这个页面的。
    • TYPE_NAVIGATE (0): 当前页面是通过点击链接,书签和表单提交,或者脚本操作,或者在 url 中直接输入地址;
    • TYPE_RELOAD (1): 点击刷新页面按钮或者通过 Location.reload()方法显示的页面;
    • TYPE_BACK_FORWARD (2): 页面通过历史记录和前进后退访问时;
    • TYPE_RESERVED (255): 任何其他方式。
  • redirectCount: 无符号短整型,表示在到达这个页面之前重定向了多少次。
timing

只读属性返回一个 PerformanceTiming 对象,这个对象包括了页面相关的性能信息。

timeOrigin

只读属性 timeOrigin 返回一个表示 the performance measurement 开始时间的高精度 timestamp

onresourcetimingbufferfull

在 resourcetimingbufferfull 事件触发时会被调用的事件处理器,参考Performance.onresourcetimingbufferfull

方法

Performance.now()

方法返回一个精确到毫秒的事件戳 DOMHighResTimeStamp

1
2
performance.now() // 38544.5
Date.now() // 1623685111685
tips: 测量程序性能时可以使用
performance.setResourceTimingBufferSize()

设置浏览器记录资源加载情况的缓冲区大小,单位是对象个数,最小为 150

Performance.mark()

在浏览器的性能缓冲区中使用给定名称添加一个 timestamp

Performance.measure()

在浏览器性能记录缓存中创建了一个名为时间戳的记录来记录两个特殊标志位(通常称为开始标志和结束标志)。

1
2
3
performance.mark('test1')
performance.mark('test2')
performance.measure('test', 'test1', 'test2') // { detail: null, duration: 0.09999999403953552, entryType: "measure", name: "test", startTime: 33062.60000000894 }
tips: 与 Performance.mark() 结合可以测量指定项目的时间
Performance.clearResourceTimings()

移除所有的记录

Performance.clearMarks()

从浏览器的 performance entry 缓存中移除声明的标记。

Performance.clearMeasures()

从浏览器的性能入口缓存区中移除声明的度量衡。

react的同步更新

react 同步更新

class 组件

每个 class 组件初始化时都会注入 updater 的属性

更新过程
  1. 构建更新节点
  2. 把节点放入更新队列
  3. 调度更新节点
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
// packages/react-reconciler/src/ReactUpdateQueue.old.js
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst)
const eventTime = requestEventTime()
const lane = requestUpdateLane(fiber) // 获取更新方式,包括同步更新

const update = createUpdate(eventTime, lane) // 构建更新
update.payload = payload
if (callback !== undefined && callback !== null) {
update.callback = callback
}

enqueueUpdate(fiber, update, lane) // 更新放入队列
const root = scheduleUpdateOnFiber(fiber, lane, eventTime) // 调度更新节点
if (root !== null) {
entangleTransitions(root, fiber, lane)
}

if (enableSchedulingProfiler) {
markStateUpdateScheduled(fiber, lane)
}
},
enqueueReplaceState(inst, payload, callback) {
// 类似与 enqueueSetState
},
enqueueForceUpdate(inst, callback) {
// 类似与 enqueueSetState
},
}
把节点放入更新队列
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
// packages/react-reconciler/src/ReactUpdateQueue.old.js
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane
) {
const updateQueue = fiber.updateQueue
if (updateQueue === null) {
// 组件卸载
return
}

const sharedQueue = updateQueue.shared

if (isInterleavedUpdate(fiber, lane)) {
const interleaved = sharedQueue.interleaved
if (interleaved === null) {
// 挂载时构成一个循环链表
update.next = update
// At the end of the current render, this queue's interleaved updates will
// be transfered to the pending queue.
pushInterleavedQueue(sharedQueue)
} else {
update.next = interleaved.next
interleaved.next = update
}
sharedQueue.interleaved = update
} else {
const pending = sharedQueue.pending
if (pending === null) {
// 挂载时构成一个循环链表
update.next = update
} else {
update.next = pending.next
pending.next = update
}
sharedQueue.pending = update
}
}
调度更新节点
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
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number
): FiberRoot | null {
checkForNestedUpdates()

const root = markUpdateLaneFromFiberToRoot(fiber, lane) // 更新从当前 fiber 标记到根节点并返回根节点
if (root === null) {
return null
}

// 标记当前节点进入更新队列
markRootUpdated(root, lane, eventTime)

if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
if (
(executionContext & CommitContext) !== NoContext &&
root === rootCommittingMutationOrLayoutEffects
) {
if (fiber.mode & ProfileMode) {
let current = fiber
while (current !== null) {
if (current.tag === Profiler) {
const { id, onNestedUpdateScheduled } = current.memoizedProps
if (typeof onNestedUpdateScheduled === 'function') {
onNestedUpdateScheduled(id)
}
}
current = current.return
}
}
}
}

// TODO: Consolidate with `isInterleavedUpdate` check
if (root === workInProgressRoot) {
// Received an update to a tree that's in the middle of rendering. Mark
// that there was an interleaved update work on this root. Unless the
// `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
// phase update. In that case, we don't treat render phase updates as if
// they were interleaved, for backwards compat reasons.
if (
deferRenderPhaseUpdateToNextBatch ||
(executionContext & RenderContext) === NoContext
) {
workInProgressRootUpdatedLanes = mergeLanes(
workInProgressRootUpdatedLanes,
lane
)
}
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// The root already suspended with a delay, which means this render
// definitely won't finish. Since we have a new update, let's mark it as
// suspended now, right before marking the incoming update. This has the
// effect of interrupting the current render and switching to the update.
// TODO: Make sure this doesn't override pings that happen while we've
// already started rendering.
markRootSuspended(root, workInProgressRootRenderLanes)
}
}

if (lane === SyncLane) {
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
performSyncWorkOnRoot(root) // 开始同步更新
} else {
ensureRootIsScheduled(root, eventTime)
if (
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode
) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
resetRenderTimer()
flushSyncCallbacksOnlyInLegacyMode()
}
}
} else {
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root, eventTime)
}

return root
}
performSyncWorkOnRoot
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
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
// This is the entry point for synchronous tasks that don't go
// through Scheduler
function performSyncWorkOnRoot(root) {
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
syncNestedUpdateFlag()
}

flushPassiveEffects()

let lanes = getNextLanes(root, NoLanes)
if (!includesSomeLane(lanes, SyncLane)) {
// 同步任务执行完毕
ensureRootIsScheduled(root, now())
return null
}

let exitStatus = renderRootSync(root, lanes)
// 错误补偿逻辑

// We now have a consistent tree. Because this is a sync render, we
// will commit it even if something suspended.
const finishedWork = root.current.alternate
root.finishedWork = finishedWork
root.finishedLanes = lanes
commitRoot(root)

// Before exiting, make sure there's a callback scheduled for the next
// pending level.
ensureRootIsScheduled(root, now())

return null
}
renderRootSync
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
// workInProgressRoot 全局变量
// file: packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function renderRootSync(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext
executionContext |= RenderContext
const prevDispatcher = pushDispatcher()

// If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes) // 更新 context
}

if (enableSchedulingProfiler) {
markRenderStarted(lanes)
}

do {
try {
workLoopSync()
break
} catch (thrownValue) {
handleError(root, thrownValue)
}
} while (true)
resetContextDependencies()
executionContext = prevExecutionContext
popDispatcher(prevDispatcher)

if (enableSchedulingProfiler) {
markRenderStopped()
}

// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null
workInProgressRootRenderLanes = NoLanes

return workInProgressRootExitStatus
}
workLoopSync
1
2
3
4
5
6
7
8
// file: packages/react-reconciler/src/ReactFiberWorkLoop.old.js
// workInProgress 全局变量
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress)
}
}
performUnitOfWork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// file: packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function performUnitOfWork(unitOfWork: Fiber): void {
// 执行更新任务
const current = unitOfWork.alternate
let next = beginWork(current, unitOfWork, subtreeRenderLanes) // child
unitOfWork.memoizedProps = unitOfWork.pendingProps
if (next === null) {
// 没有下一个任务时表示任务已经做完
completeUnitOfWork(unitOfWork)
} else {
workInProgress = next
}

ReactCurrentOwner.current = null
}
beginWork
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
// file: packages/react-reconciler/src/ReactFiberBeginWork.old.js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
let updateLanes = workInProgress.lanes

if (current !== null) {
const oldProps = current.memoizedProps
const newProps = workInProgress.pendingProps
if (oldProps !== newProps || hasLegacyContextChanged()) {
// props 更新或 context 更新
didReceiveUpdate = true
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false
// 此次渲染的更新和等待的更新没有任何交集,新增等待的更新等待下次执行
} else {
// 其它更新逻辑
}
} else {
didReceiveUpdate = false
}

// 清空
workInProgress.lanes = NoLanes
switch (workInProgress.tag) {
case ClassComponent: {
const Component = workInProgress.type
const unresolvedProps = workInProgress.pendingProps
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps)
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes
)
}
// 其它类型组件更新逻辑
}
}
updateClassComponent
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
// file: packages/react-reconciler/src/ReactFiberBeginWork.old.js
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes
) {
// 提取 context
let hasContext
if (isLegacyContextProvider(Component)) {
hasContext = true
pushLegacyContextProvider(workInProgress)
} else {
hasContext = false
}
prepareToReadContext(workInProgress, renderLanes)

const instance = workInProgress.stateNode
let shouldUpdate
if (instance === null) {
// 挂载
if (current !== null) {
// A class component without an instance only mounts if it suspended
// inside a non-concurrent tree, in an inconsistent state. We want to
// treat it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null
workInProgress.alternate = null
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.flags |= Placement
}
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, Component, nextProps)
mountClassInstance(workInProgress, Component, nextProps, renderLanes)
shouldUpdate = true
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes
)
} else {
// 更新
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes
)
}
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes
) // child
return nextUnitOfWork
}
constructClassInstance
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
// file: packages/react-reconciler/src/ReactFiberClassComponent.old.js
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any
): any {
let isLegacyContextConsumer = false
let unmaskedContext = emptyContextObject
let context = emptyContextObject
const contextType = ctor.contextType

// 提取 context
if (typeof contextType === 'object' && contextType !== null) {
context = readContext(contextType)
} else if (!disableLegacyContext) {
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true)
const contextTypes = ctor.contextTypes
isLegacyContextConsumer =
contextTypes !== null && contextTypes !== undefined
context = isLegacyContextConsumer
? getMaskedContext(workInProgress, unmaskedContext)
: emptyContextObject
}

// 构建组件实例
const instance = new ctor(props, context)
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null)
// fiber 注入更新队列和实例信息,存储实例和 fiber 映射
adoptClassInstance(workInProgress, instance)

// 缓存 context
if (isLegacyContextConsumer) {
cacheContext(workInProgress, unmaskedContext, context)
}

return instance
}
adoptClassInstance
1
2
3
4
5
6
7
// file: packages/react-reconciler/src/ReactFiberClassComponent.old.js
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater // 更新队列
workInProgress.stateNode = instance // 存储组件实例
//构建 fiber 和组件实例的映射
setInstance(instance, workInProgress)
}
mountClassInstance
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
// file: packages/react-reconciler/src/ReactFiberClassComponent.old.js
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderLanes: Lanes
): void {
const instance = workInProgress.stateNode
instance.props = newProps
instance.state = workInProgress.memoizedState
instance.refs = emptyRefsObject

// 初始化队列
initializeUpdateQueue(workInProgress)

// 获取 context
const contextType = ctor.contextType
if (typeof contextType === 'object' && contextType !== null) {
instance.context = readContext(contextType)
} else if (disableLegacyContext) {
instance.context = emptyContextObject
} else {
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true)
instance.context = getMaskedContext(workInProgress, unmaskedContext)
}

// 更新 state
instance.state = workInProgress.memoizedState

// 执行 getDerivedStateFromProps 更新 state
const getDerivedStateFromProps = ctor.getDerivedStateFromProps
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps
)
instance.state = workInProgress.memoizedState
}

// 执行 componentWillMount 生命周期
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
callComponentWillMount(workInProgress, instance)
// componentWillMount 执行 setState 时立即更新 state
processUpdateQueue(workInProgress, newProps, instance, renderLanes)
instance.state = workInProgress.memoizedState
}

if (typeof instance.componentDidMount === 'function') {
let fiberFlags: Flags = Update
if (enableSuspenseLayoutEffectSemantics) {
fiberFlags |= LayoutStatic
}
workInProgress.flags |= fiberFlags
}
}
finishClassComponent
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
// file: packages/react-reconciler/src/ReactFiberClassComponent.old.js
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderLanes: Lanes
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress)

const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags

if (!shouldUpdate && !didCaptureError) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
}

const instance = workInProgress.stateNode

// Rerender
ReactCurrentOwner.current = workInProgress
let nextChildren
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
// 错误处理
} else {
nextChildren = instance.render()
}

reconcileChildren(current, workInProgress, nextChildren, renderLanes)

// 记录 state
workInProgress.memoizedState = instance.state

return workInProgress.child
}
reconcileChildren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// file: packages/react-reconciler/src/ReactFiberClassComponent.old.js
function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes
) {
if (current === null) {
// 挂载
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes
)
} else {
// 更新
}
}
ChildReconciler
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// file: packages/react-reconciler/src/ReactChildFiber.old.js
const mountChildFibers = ChildReconciler(false)

function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
if (!shouldTrackSideEffects) {
return
}
// 更新逻辑
}

function deleteRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber | null
): null {
if (!shouldTrackSideEffects) {
return null
}
// 更新逻辑
}

function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
const clone = createWorkInProgress(fiber, pendingProps) // 构建一个新的 fiber 节点
clone.index = 0
clone.sibling = null
return clone
}

function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number
): number {
newFiber.index = newIndex
if (!shouldTrackSideEffects) {
return lastPlacedIndex
}
// 更新逻辑
}

// 返回 fiber
function placeSingleChild(newFiber: Fiber): Fiber {
return newFiber
}

function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
lanes: Lanes
): Fiber {
// There's no need to check for keys on text nodes since we don't have a
// way to define them.
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
// We already have an existing node so let's just update it and delete
// the rest.
deleteRemainingChildren(returnFiber, currentFirstChild.sibling)
const existing = useFiber(currentFirstChild, textContent)
existing.return = returnFiber
return existing
}
// The existing first child is not a text node so we need to create one
// and delete the existing ones.
deleteRemainingChildren(returnFiber, currentFirstChild)
const created = createFiberFromText(textContent, returnFiber.mode, lanes)
created.return = returnFiber
return created
}

// 构建 fiber
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes
): Fiber {
const key = element.key
let child = currentFirstChild
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
const elementType = element.type
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
deleteRemainingChildren(returnFiber, child.sibling)
const existing = useFiber(child, element.props.children)
existing.return = returnFiber
return existing
}
} else {
if (
child.elementType === elementType ||
(enableLazyElements &&
typeof elementType === 'object' &&
elementType !== null &&
elementType.$$typeof === REACT_LAZY_TYPE &&
resolveLazy(elementType) === child.type)
) {
deleteRemainingChildren(returnFiber, child.sibling)
const existing = useFiber(child, element.props)
existing.ref = coerceRef(returnFiber, child, element)
existing.return = returnFiber
return existing
}
}
// Didn't match.
deleteRemainingChildren(returnFiber, child)
break
} else {
deleteChild(returnFiber, child)
}
child = child.sibling
}

if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key
)
created.return = returnFiber
return created
} else {
const created = createFiberFromElement(element, returnFiber.mode, lanes)
created.ref = coerceRef(returnFiber, currentFirstChild, element)
created.return = returnFiber
return created
}
}

// 构建 children fiber
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes
): Fiber | null {
// 处理 <>{[...]}</> 和 <>...</> 组件,从其中提取 children
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children
}

// 对象类型,为 react 组件
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes
)
)
// 其它 react 组件类型
}

// 数组、迭代器,支持 promise 或者 generate
}

if (typeof newChild === 'string' || typeof newChild === 'number') {
// 处理字符串或数字类型
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes
)
)
}

// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild)
}

return reconcileChildFibers
}
completeUnitOfWork
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
// file: packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork
do {
const current = completedWork.alternate // 更新后的 fiber
const returnFiber = completedWork.return // 父节点 fiber

if ((completedWork.flags & Incomplete) === NoFlags) {
// 任务未完成
let next = completeWork(current, completedWork, subtreeRenderLanes)
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
workInProgress = next
return
}
} else {
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
const next = unwindWork(completedWork, subtreeRenderLanes)

// Because this fiber did not complete, don't reset its expiration time.

if (next !== null) {
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
next.flags &= HostEffectMask
workInProgress = next
return
}

if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its subtree flags.
returnFiber.flags |= Incomplete
returnFiber.subtreeFlags = NoFlags
returnFiber.deletions = null
}
}

const siblingFiber = completedWork.sibling
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber
return
}
// Otherwise, return to the parent
completedWork = returnFiber
// Update the next thing we're working on in case something throws.
workInProgress = completedWork
} while (completedWork !== null)

// We've reached the root.
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted
}
}
commitRoot

调用 commitRootImpl 提交更新

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
function commitRootImpl(root, renderPriorityLevel) {
do {
// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
// means `flushPassiveEffects` will sometimes result in additional
// passive effects. So we need to keep flushing in a loop until there are
// no more pending effects.
// TODO: Might be better if `flushPassiveEffects` did not automatically
// flush synchronous work at the end, to avoid factoring hazards like this.
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);

const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;

if (finishedWork === null) {
return null;
}
root.finishedWork = null;
root.finishedLanes = NoLanes;

// commitRoot never returns a continuation; it always finishes synchronously.
// So we can clear these now to allow a new callback to be scheduled.
root.callbackNode = null;
root.callbackPriority = NoLane;

// Update the first and last pending times on this root. The new first
// pending time is whatever is left on the root fiber.
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);

if (root === workInProgressRoot) {
// We can reset these now that they are finished.
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
}

// If there are pending passive effects, schedule a callback to process them.
// Do this as early as possible, so it is queued before anything else that
// might get scheduled in the commit phase. (See #16714.)
// TODO: Delete all other places that schedule the passive effect callback
// They're redundant.
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}

// Check if there are any effects in the whole tree.
// TODO: This is left over from the effect list implementation, where we had
// to check for the existence of `firstEffect` to satsify Flow. I think the
// only other reason this optimization exists is because it affects profiling.
// Reconsider whether this is necessary.
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;

if (subtreeHasEffects || rootHasEffect) {
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 0;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);

const prevExecutionContext = executionContext;
executionContext |= CommitContext;

// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;

// The commit phase is broken into several sub-phases. We do a separate pass
// of the effect list for each phase: all mutation effects come before all
// layout effects, and so on.

// The first phase a "before mutation" phase. We use this phase to read the
// state of the host tree right before we mutate it. This is where
// getSnapshotBeforeUpdate is called.
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);

if (enableProfilerTimer) {
// Mark the current commit time to be shared by all Profilers in this
// batch. This enables them to be grouped later.
recordCommitTime();
}

if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
// Track the root here, rather than in commitLayoutEffects(), because of ref setters.
// Updates scheduled during ref detachment should also be flagged.
rootCommittingMutationOrLayoutEffects = root;
}

// The next phase is the mutation phase, where we mutate the host tree.
commitMutationEffects(root, finishedWork, lanes);

if (shouldFireAfterActiveInstanceBlur) {
afterActiveInstanceBlur();
}
resetAfterCommit(root.containerInfo);

// The work-in-progress tree is now the current tree. This must come after
// the mutation phase, so that the previous tree is still current during
// componentWillUnmount, but before the layout phase, so that the finished
// work is current during componentDidMount/Update.
root.current = finishedWork;
commitLayoutEffects(finishedWork, root, lanes);

if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
rootCommittingMutationOrLayoutEffects = null;
}

// Tell Scheduler to yield at the end of the frame, so the browser has an
// opportunity to paint.
requestPaint();

executionContext = prevExecutionContext;

// Reset the priority to the previous non-sync value.
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
} else {
// No effects.
root.current = finishedWork;
// Measure these anyway so the flamegraph explicitly shows that there were
// no effects.
// TODO: Maybe there's a better way to report this.
if (enableProfilerTimer) {
recordCommitTime();
}
}

const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

if (rootDoesHavePassiveEffects) {
// This commit has passive effects. Stash a reference to them. But don't
// schedule a callback until after flushing layout work.
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
}

// Read this again, since an effect might have updated it
remainingLanes = root.pendingLanes;

// Check if there's remaining work on this root
if (remainingLanes === NoLanes) {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
}


if (includesSomeLane(remainingLanes, (SyncLane: Lane))) {
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
markNestedUpdateScheduled();
}

// Count the number of times the root synchronously re-renders without
// finishing. If there are too many, it indicates an infinite update loop.
if (root === rootWithNestedUpdates) {
nestedUpdateCount++;
} else {
nestedUpdateCount = 0;
rootWithNestedUpdates = root;
}
} else {
nestedUpdateCount = 0;
}

// Always call this before exiting `commitRoot`, to ensure that any
// additional work on this root is scheduled.
ensureRootIsScheduled(root, now());

if (hasUncaughtError) {
hasUncaughtError = false;
const error = firstUncaughtError;
firstUncaughtError = null;
throw error;
}

if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
if (enableSchedulingProfiler) {
markCommitStopped();
}

// This is a legacy edge case. We just committed the initial mount of
// a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
// synchronously, but layout updates should be deferred until the end
// of the batch.
return null;
}

// If the passive effects are the result of a discrete render, flush them
// synchronously at the end of the current task so that the result is
// immediately observable. Otherwise, we assume that they are not
// order-dependent and do not need to be observed by external systems, so we
// can wait until after paint.
// TODO: We can optimize this by not scheduling the callback earlier. Since we
// currently schedule the callback in multiple places, will wait until those
// are consolidated.
if (
includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
root.tag !== LegacyRoot
) {
flushPassiveEffects();
}

// If layout work was scheduled, flush it now.
flushSyncCallbacks();

if (enableSchedulingProfiler) {
markCommitStopped();
}

return null;
}

flushPassiveEffects

调用 flushPassiveEffectsImpl

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
function flushPassiveEffectsImpl() {
if (rootWithPendingPassiveEffects === null) {
return false;
}

const root = rootWithPendingPassiveEffects;
const lanes = pendingPassiveEffectsLanes;
rootWithPendingPassiveEffects = null;
// TODO: This is sometimes out of sync with rootWithPendingPassiveEffects.
// Figure out why and fix it. It's not causing any known issues (probably
// because it's only used for profiling), but it's a refactor hazard.
pendingPassiveEffectsLanes = NoLanes;

const prevExecutionContext = executionContext;
executionContext |= CommitContext;

commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);

// TODO: Move to commitPassiveMountEffects
if (enableProfilerTimer && enableProfilerCommitHooks) {
const profilerEffects = pendingPassiveProfilerEffects;
pendingPassiveProfilerEffects = [];
for (let i = 0; i < profilerEffects.length; i++) {
const fiber = ((profilerEffects[i]: any): Fiber);
commitPassiveEffectDurations(root, fiber);
}
}

executionContext = prevExecutionContext;
flushSyncCallbacks();

// If additional passive effects were scheduled, increment a counter. If this
// exceeds the limit, we'll fire a warning.
nestedPassiveUpdateCount =
rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;

if (enableProfilerTimer && enableProfilerCommitHooks) {
const stateNode = root.current.stateNode;
stateNode.effectDuration = 0;
stateNode.passiveEffectDuration = 0;
}

return true;
}
commitPassiveUnmountEffects

执行生命周期钩子,当进入节点时立即执行生命周期函数
遍历顺序为: 根节点—> 父节点 —> 叶子节点 —> 兄弟叶子节点 —> 父节点 —> 根节点

调用 commitPassiveUnmountEffects_begin 开启操作,commitPassiveUnmountEffects_complete 完成操作

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// file: packages/react-reconciler/src/ReactFiberCommitWork.old.js
// nextEffect 全局变量
function commitPassiveUnmountEffects_begin() {
// curr -> child -> child
while (nextEffect !== null) {
const fiber = nextEffect
const child = fiber.child

if ((nextEffect.flags & ChildDeletion) !== NoFlags) {
const deletions = fiber.deletions
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const fiberToDelete = deletions[i]
nextEffect = fiberToDelete
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
fiberToDelete,
fiber
)
}

if (deletedTreeCleanUpLevel >= 1) {
// A fiber was deleted from this parent fiber, but it's still part of
// the previous (alternate) parent fiber's list of children. Because
// children are a linked list, an earlier sibling that's still alive
// will be connected to the deleted fiber via its `alternate`:
//
// live fiber
// --alternate--> previous live fiber
// --sibling--> deleted fiber
//
// We can't disconnect `alternate` on nodes that haven't been deleted
// yet, but we can disconnect the `sibling` and `child` pointers.
const previousFiber = fiber.alternate
if (previousFiber !== null) {
let detachedChild = previousFiber.child
if (detachedChild !== null) {
previousFiber.child = null
do {
const detachedSibling = detachedChild.sibling
detachedChild.sibling = null
detachedChild = detachedSibling
} while (detachedChild !== null)
}
}
}

nextEffect = fiber
}
}

if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber) // 确保父节点指向 fiber
nextEffect = child
} else {
commitPassiveUnmountEffects_complete()
}
}
}

function commitPassiveUnmountEffects_complete() {
// 若存在兄弟节点则遍历下一个兄弟节点,否则返回父节点
while (nextEffect !== null) {
const fiber = nextEffect
if ((fiber.flags & Passive) !== NoFlags) {
commitPassiveUnmountOnFiber(fiber)
}

const sibling = fiber.sibling
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return)
nextEffect = sibling
return
}
nextEffect = fiber.return
}
}

function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectListUnmount(
HookPassive | HookHasEffect,
finishedWork,
finishedWork.return,
);
break;
}
}
}

function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
deletedSubtreeRoot: Fiber,
nearestMountedAncestor: Fiber | null
) {
// p-> child -> child
while (nextEffect !== null) {
const fiber = nextEffect
// Deletion effects fire in parent -> child order
// TODO: Check if fiber has a PassiveStatic flag
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor)

const child = fiber.child
// TODO: Only traverse subtree if it has a PassiveStatic flag. (But, if we
// do this, still need to handle `deletedTreeCleanUpLevel` correctly.)
if (child !== null) {
ensureCorrectReturnPointer(child, fiber)
nextEffect = child
} else {
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
deletedSubtreeRoot
)
}
}
}

function commitPassiveUnmountInsideDeletedTreeOnFiber(
current: Fiber,
nearestMountedAncestor: Fiber | null
): void {
switch (current.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectListUnmount(HookPassive, current, nearestMountedAncestor)
break
}
}
}

// 销毁组件,要销毁的组件存在 updateQueue 中
function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null,
) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy); // 调用 destroy 销毁组件
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}

function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
deletedSubtreeRoot: Fiber
) {
while (nextEffect !== null) {
const fiber = nextEffect
const sibling = fiber.sibling
const returnFiber = fiber.return

if (deletedTreeCleanUpLevel >= 2) {
// Recursively traverse the entire deleted tree and clean up fiber fields.
// This is more aggressive than ideal, and the long term goal is to only
// have to detach the deleted tree at the root.
detachFiberAfterEffects(fiber)
if (fiber === deletedSubtreeRoot) {
nextEffect = null
return
}
} else {
// This is the default branch (level 0). We do not recursively clear all
// the fiber fields. Only the root of the deleted subtree.
if (fiber === deletedSubtreeRoot) {
detachFiberAfterEffects(fiber)
nextEffect = null
return
}
}

if (sibling !== null) {
ensureCorrectReturnPointer(sibling, returnFiber)
nextEffect = sibling
return
}

nextEffect = returnFiber
}
}

function detachFiberAfterEffects(fiber: Fiber) {
const alternate = fiber.alternate
if (alternate !== null) {
fiber.alternate = null
detachFiberAfterEffects(alternate)
}

// Note: Defensively using negation instead of < in case
// `deletedTreeCleanUpLevel` is undefined.
if (!(deletedTreeCleanUpLevel >= 2)) {
// This is the default branch (level 0).
fiber.child = null
fiber.deletions = null
fiber.dependencies = null
fiber.memoizedProps = null
fiber.memoizedState = null
fiber.pendingProps = null
fiber.sibling = null
fiber.stateNode = null
fiber.updateQueue = null
} else {
// Clear cyclical Fiber fields. This level alone is designed to roughly
// approximate the planned Fiber refactor. In that world, `setState` will be
// bound to a special "instance" object instead of a Fiber. The Instance
// object will not have any of these fields. It will only be connected to
// the fiber tree via a single link at the root. So if this level alone is
// sufficient to fix memory issues, that bodes well for our plans.
fiber.child = null
fiber.deletions = null
fiber.sibling = null

// The `stateNode` is cyclical because on host nodes it points to the host
// tree, which has its own pointers to children, parents, and siblings.
// The other host nodes also point back to fibers, so we should detach that
// one, too.
if (fiber.tag === HostComponent) {
const hostInstance: Instance = fiber.stateNode
if (hostInstance !== null) {
detachDeletedInstance(hostInstance) // 删除 react 相关属性,包括事件、容器等
}
}
fiber.stateNode = null

if (deletedTreeCleanUpLevel >= 3) {
// Theoretically, nothing in here should be necessary, because we already
// disconnected the fiber from the tree. So even if something leaks this
// particular fiber, it won't leak anything else
//
// The purpose of this branch is to be super aggressive so we can measure
// if there's any difference in memory impact. If there is, that could
// indicate a React leak we don't know about.
fiber.return = null
fiber.dependencies = null
fiber.memoizedProps = null
fiber.memoizedState = null
fiber.pendingProps = null
fiber.stateNode = null
// TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
fiber.updateQueue = null
}
}
}
commitBeforeMutationEffects

执行生命周期钩子,当进入节点时立即执行生命周期函数
遍历顺序为: 根节点—> 父节点 —> 叶子节点 —> 兄弟叶子节点 —> 父节点 —> 根节点

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
// file: packages/react-reconciler/src/ReactFiberCommitWork.old.js
// nextEffect, focusedInstanceHandle 全局变量
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber
) {
focusedInstanceHandle = prepareForCommit(root.containerInfo) // 查找活跃的的节点

nextEffect = firstChild
commitBeforeMutationEffects_begin()

// We no longer need to track the active instance fiber
const shouldFire = shouldFireAfterActiveInstanceBlur
shouldFireAfterActiveInstanceBlur = false
focusedInstanceHandle = null

return shouldFire
}

function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect

// TODO: Should wrap this in flags check, too, as optimization
const deletions = fiber.deletions
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i]
commitBeforeMutationEffectsDeletion(deletion)
}
}

const child = fiber.child
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
ensureCorrectReturnPointer(child, fiber)
nextEffect = child
} else {
commitBeforeMutationEffects_complete()
}
}
}

function commitBeforeMutationEffects_complete() {
// 先兄弟节点再父节点
while (nextEffect !== null) {
const fiber = nextEffect
commitBeforeMutationEffectsOnFiber(fiber)
const sibling = fiber.sibling
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return)
nextEffect = sibling
return
}

nextEffect = fiber.return
}
}

function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate
const flags = finishedWork.flags

if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
// Check to see if the focused element was inside of a hidden (Suspense) subtree.
// TODO: Move this out of the hot path using a dedicated effect tag.
if (
finishedWork.tag === SuspenseComponent &&
isSuspenseBoundaryBeingHidden(current, finishedWork) &&
doesFiberContain(finishedWork, focusedInstanceHandle)
) {
shouldFireAfterActiveInstanceBlur = true
beforeActiveInstanceBlur(finishedWork)
}
}

if ((flags & Snapshot) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
break
}
case ClassComponent: {
if (current !== null) {
const prevProps = current.memoizedProps
const prevState = current.memoizedState
const instance = finishedWork.stateNode
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState
)
instance.__reactInternalSnapshotBeforeUpdate = snapshot
}
break
}
case HostRoot: {
if (supportsMutation) {
const root = finishedWork.stateNode
clearContainer(root.containerInfo)
}
break
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
break
default: {
}
}
}
}

function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
// TODO (effects) It would be nice to avoid calling doesFiberContain()
// Maybe we can repurpose one of the subtreeFlags positions for this instead?
// Use it to store which part of the tree the focused instance is in?
// This assumes we can safely determine that instance during the "render" phase.
if (doesFiberContain(deletion, focusedInstanceHandle)) {
shouldFireAfterActiveInstanceBlur = true
beforeActiveInstanceBlur(deletion)
}
}
commitMutationEffects

构建 dom 节点,在离开节点时开始 dom 构建
遍历顺序为: 根节点—> 父节点 —> 叶子节点 —> 兄弟叶子节点 —> 父节点 —> 根节点

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
// file: packages/react-reconciler/src/ReactFiberCommitWork.old.js
// nextEffect, focusedInstanceHandle,inProgressLanes,inProgressRoot 全局变量
function commitMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes
) {
inProgressLanes = committedLanes
inProgressRoot = root
nextEffect = firstChild

commitMutationEffects_begin(root)

inProgressLanes = null
inProgressRoot = null
}

function commitMutationEffects_begin(root: FiberRoot) {
while (nextEffect !== null) {
const fiber = nextEffect

// TODO: Should wrap this in flags check, too, as optimization
const deletions = fiber.deletions
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i]
commitDeletion(root, childToDelete, fiber)
}
}

const child = fiber.child
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber)
nextEffect = child
} else {
commitMutationEffects_complete(root)
}
}
}

function commitMutationEffects_complete(root: FiberRoot) {
while (nextEffect !== null) {
const fiber = nextEffect
commitMutationEffectsOnFiber(fiber, root)

const sibling = fiber.sibling
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return)
nextEffect = sibling
return
}

nextEffect = fiber.return
}
}

function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
const flags = finishedWork.flags

if (flags & ContentReset) {
commitResetTextContent(finishedWork)
}

if (flags & Ref) {
const current = finishedWork.alternate
if (current !== null) {
commitDetachRef(current)
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (finishedWork.tag === ScopeComponent) {
commitAttachRef(finishedWork)
}
}
}

// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every possible
// bitmap value, we remove the secondary effects from the effect tag and
// switch on that value.
const primaryFlags = flags & (Placement | Update | Hydrating)
switch (primaryFlags) {
case Placement: {
commitPlacement(finishedWork)
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
finishedWork.flags &= ~Placement
break
}
case PlacementAndUpdate: {
// Placement
commitPlacement(finishedWork)
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
finishedWork.flags &= ~Placement

// Update
const current = finishedWork.alternate
commitWork(current, finishedWork)
break
}
case Hydrating: {
finishedWork.flags &= ~Hydrating
break
}
case HydratingAndUpdate: {
finishedWork.flags &= ~Hydrating

// Update
const current = finishedWork.alternate
commitWork(current, finishedWork)
break
}
case Update: {
const current = finishedWork.alternate
commitWork(current, finishedWork)
break
}
}
}

function commitWork(current: Fiber | null, finishedWork: Fiber): void {
if (!supportsMutation) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// Layout effects are destroyed during the mutation phase so that all
// destroy functions for all fibers are called before any create functions.
// This prevents sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.

commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);

return;
}
case Profiler: {
return;
}
case SuspenseComponent: {
commitSuspenseComponent(finishedWork);
attachSuspenseRetryListeners(finishedWork);
return;
}
case SuspenseListComponent: {
attachSuspenseRetryListeners(finishedWork);
return;
}
case HostRoot: {
if (supportsHydration) {
const root: FiberRoot = finishedWork.stateNode;
if (root.hydrate) {
// We've just hydrated. No need to hydrate again.
root.hydrate = false;
commitHydratedContainer(root.containerInfo);
}
}
break;
}
case OffscreenComponent:
case LegacyHiddenComponent: {
return;
}
}

commitContainer(finishedWork);
return;
}

switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// Layout effects are destroyed during the mutation phase so that all
// destroy functions for all fibers are called before any create functions.
// This prevents sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.

commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
}
return;

case ClassComponent: {
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
if (supportsHydration) {
const root: FiberRoot = finishedWork.stateNode;
if (root.hydrate) {
// We've just hydrated. No need to hydrate again.
root.hydrate = false;
commitHydratedContainer(root.containerInfo);
}
}
return;
}
case Profiler: {
return;
}
case SuspenseComponent: {
commitSuspenseComponent(finishedWork);
attachSuspenseRetryListeners(finishedWork);
return;
}
case SuspenseListComponent: {
attachSuspenseRetryListeners(finishedWork);
return;
}
case IncompleteClassComponent: {
return;
}
case ScopeComponent: {
if (enableScopeAPI) {
const scopeInstance = finishedWork.stateNode;
prepareScopeUpdate(scopeInstance, finishedWork);
return;
}
break;
}
case OffscreenComponent:
case LegacyHiddenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
hideOrUnhideAllChildren(finishedWork, isHidden);
return;
}
}
}

function commitPlacement(finishedWork: Fiber): void {
if (!supportsMutation) {
return;
}

// Recursively insert all host nodes into the parent.
const parentFiber = getHostParentFiber(finishedWork);

// Note: these two variables *must* always be updated together.
let parent;
let isContainer;
const parentStateNode = parentFiber.stateNode;
switch (parentFiber.tag) {
case HostComponent:
parent = parentStateNode;
isContainer = false;
break;
case HostRoot:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
// eslint-disable-next-line-no-fallthrough
default:
}
if (parentFiber.flags & ContentReset) {
// Reset the text content of the parent before doing any insertions
resetTextContent(parent);
// Clear ContentReset from the effect tag
parentFiber.flags &= ~ContentReset;
}

const before = getHostSibling(finishedWork);
// 插入 dom 元素
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
insertOrAppendPlacementNode(finishedWork, before, parent);
}
}

function isHostParent(fiber: Fiber): boolean {
return (
fiber.tag === HostComponent ||
fiber.tag === HostRoot ||
fiber.tag === HostPortal
);
}

function getHostSibling(fiber: Fiber): ?Instance {
// We're going to search forward into the tree until we find a sibling host
// node. Unfortunately, if multiple insertions are done in a row we have to
// search past them. This leads to exponential search for the next sibling.
// TODO: Find a more efficient way to do this.
let node: Fiber = fiber;
siblings: while (true) {
// If we didn't find anything, let's try the next sibling.
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// If we pop out of the root or hit the parent the fiber we are the
// last sibling.
return null;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
while (
node.tag !== HostComponent &&
node.tag !== HostText &&
node.tag !== DehydratedFragment
) {
// If it is not host node and, we might have a host node inside it.
// Try to search down until we find one.
if (node.flags & Placement) {
// If we don't have a child, try the siblings instead.
continue siblings;
}
// If we don't have a child, try the siblings instead.
// We also skip portals because they are not part of this host tree.
if (node.child === null || node.tag === HostPortal) {
continue siblings;
} else {
node.child.return = node;
node = node.child;
}
}
// Check if this host node is stable or about to be placed.
if (!(node.flags & Placement)) {
// Found it!
return node.stateNode;
}
}
}

function insertOrAppendPlacementNodeIntoContainer(
node: Fiber,
before: ?Instance,
parent: Container,
): void {
const {tag} = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const stateNode = node.stateNode;
if (before) {
insertInContainerBefore(parent, stateNode, before);
} else {
appendChildToContainer(parent, stateNode);
}
} else if (tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else {
const child = node.child;
if (child !== null) {
insertOrAppendPlacementNodeIntoContainer(child, before, parent);
let sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}

function insertOrAppendPlacementNode(
node: Fiber,
before: ?Instance,
parent: Instance,
): void {
const {tag} = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const stateNode = node.stateNode;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else if (tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else {
const child = node.child;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}

commitLayoutEffects

执行生命周期钩子,在离开当前节点时调用钩子函数
遍历顺序为: 根节点—> 父节点 —> 叶子节点 —> 兄弟叶子节点 —> 父节点 —> 根节点

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
148
149
150
151
152
153
154
155
// file: packages/react-reconciler/src/ReactFiberCommitWork.old.js
// nextEffect, focusedInstanceHandle,inProgressLanes,inProgressRoot 全局变量
function commitLayoutEffects(
finishedWork: Fiber,
root: FiberRoot,
committedLanes: Lanes
): void {
inProgressLanes = committedLanes
inProgressRoot = root
nextEffect = finishedWork

commitLayoutEffects_begin(finishedWork, root, committedLanes)

inProgressLanes = null
inProgressRoot = null
}

function commitLayoutEffects_begin(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes
) {
// Suspense layout effects semantics don't change for legacy roots.
const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode

while (nextEffect !== null) {
const fiber = nextEffect
const firstChild = fiber.child

if (enableSuspenseLayoutEffectSemantics && isModernRoot) {
// Keep track of the current Offscreen stack's state.
if (fiber.tag === OffscreenComponent) {
const current = fiber.alternate
const wasHidden = current !== null && current.memoizedState !== null
const isHidden = fiber.memoizedState !== null

const newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden
const newOffscreenSubtreeWasHidden =
wasHidden || offscreenSubtreeWasHidden

if (
newOffscreenSubtreeIsHidden !== offscreenSubtreeIsHidden ||
newOffscreenSubtreeWasHidden !== offscreenSubtreeWasHidden
) {
const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden

// Traverse the Offscreen subtree with the current Offscreen as the root.
offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden
offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden
commitLayoutEffects_begin(
fiber, // New root; bubble back up to here and stop.
root,
committedLanes
)

// Restore Offscreen state and resume in our-progress traversal.
nextEffect = fiber
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes)

continue
}
}
}

if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber)
nextEffect = firstChild
} else {
if (enableSuspenseLayoutEffectSemantics && isModernRoot) {
const visibilityChanged =
!offscreenSubtreeIsHidden && offscreenSubtreeWasHidden

// TODO (Offscreen) Also check: subtreeFlags & LayoutStatic
if (visibilityChanged && firstChild !== null) {
// We've just shown or hidden a Offscreen tree that contains layout effects.
// We only enter this code path for subtrees that are updated,
// because newly mounted ones would pass the LayoutMask check above.
ensureCorrectReturnPointer(firstChild, fiber)
nextEffect = firstChild
continue
}
}

commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes)
}
}
}

function commitLayoutMountEffects_complete(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes
) {
// Suspense layout effects semantics don't change for legacy roots.
const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode

while (nextEffect !== null) {
const fiber = nextEffect

if (
enableSuspenseLayoutEffectSemantics &&
isModernRoot &&
offscreenSubtreeWasHidden &&
!offscreenSubtreeIsHidden
) {
// Inside of an Offscreen subtree that changed visibility during this commit.
// If this subtree was hidden, layout effects will have already been destroyed (during mutation phase)
// but if it was just shown, we need to (re)create the effects now.
// TODO (Offscreen) Check: flags & LayoutStatic
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
safelyCallCommitHookLayoutEffectListMount(fiber, fiber.return)
break
}
case ClassComponent: {
const instance = fiber.stateNode
if (typeof instance.componentDidMount === 'function') {
safelyCallComponentDidMount(fiber, fiber.return, instance)
}
break
}
}

// TODO (Offscreen) Check flags & RefStatic
switch (fiber.tag) {
case ClassComponent:
case HostComponent:
safelyAttachRef(fiber, fiber.return)
break
}
} else if ((fiber.flags & LayoutMask) !== NoFlags) {
const current = fiber.alternate
commitLayoutEffectOnFiber(root, current, fiber, committedLanes)
}

if (fiber === subtreeRoot) {
nextEffect = null
return
}

const sibling = fiber.sibling
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return)
nextEffect = sibling
return
}

nextEffect = fiber.return
}
}

react中的事件

react 中的事件

初始化

react 事件初始化

  1. react-dom 模块的 src/client/ReactDOM 中引入 ReactDOMEventHandle 文件,把该文件中的 createEventHandle 函数导出为 unstable_createEventHandle 以供第三方使用

  2. ReactDOMEventHandle 文件中引入 events/DOMPluginEventSystem 文件,在模块中注册了顶级事件

    1
    2
    3
    4
    5
    6
    // file: packages/react-dom/src/events/DOMPluginEventSystem.js
    SimpleEventPlugin.registerEvents()
    EnterLeaveEventPlugin.registerEvents()
    ChangeEventPlugin.registerEvents()
    SelectEventPlugin.registerEvents()
    BeforeInputEventPlugin.registerEvents()
  3. 记录 dom 事件和 react 事件的映射关系

    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
    // file: packages/react-dom/src/events/DOMEventProperties.js
    export const topLevelEventsToReactNames = new Map()

    // 记录 dom event 和 react event 的映射
    function registerSimpleEvent(domEventName, reactName) {
    topLevelEventsToReactNames.set(domEventName, reactName)
    registerTwoPhaseEvent(reactName, [domEventName])
    }

    export function registerSimpleEvents() {
    for (let i = 0; i < simpleEventPluginEvents.length; i++) {
    const eventName = ((simpleEventPluginEvents[i]: any): string)
    const domEventName = ((eventName.toLowerCase(): any): DOMEventName)
    const capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1)
    registerSimpleEvent(domEventName, 'on' + capitalizedEvent)
    }
    // Special cases where event names don't match.
    registerSimpleEvent(ANIMATION_END, 'onAnimationEnd')
    registerSimpleEvent(ANIMATION_ITERATION, 'onAnimationIteration')
    registerSimpleEvent(ANIMATION_START, 'onAnimationStart')
    registerSimpleEvent('dblclick', 'onDoubleClick')
    registerSimpleEvent('focusin', 'onFocus')
    registerSimpleEvent('focusout', 'onBlur')
    registerSimpleEvent(TRANSITION_END, 'onTransitionEnd')
    }
  4. events/EventRegistry 模块中调用方法把注册的事件放入存储的 Set 和对象中,allNativeEvents Set 负责存储所有注册过的 dom 事件名称,registrationNameDependencies{[reactEventName]: [nativeEventName] }负责存储注册的事件依赖,同时注册了冒泡事件和捕获事件

    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
    // file: packages/react-dom/src/events/EventRegistry.js
    export const allNativeEvents: Set<DOMEventName> = new Set()

    /**
    * Mapping from registration name to event name
    */
    export const registrationNameDependencies = {}

    export function registerTwoPhaseEvent(
    registrationName: string,
    dependencies: Array<DOMEventName>
    ): void {
    registerDirectEvent(registrationName, dependencies)
    registerDirectEvent(registrationName + 'Capture', dependencies)
    }

    export function registerDirectEvent(
    registrationName: string,
    dependencies: Array<DOMEventName>
    ) {
    registrationNameDependencies[registrationName] = dependencies
    for (let i = 0; i < dependencies.length; i++) {
    allNativeEvents.add(dependencies[i])
    }
    }
  5. 合成事件的初始化完毕

注册原生事件

react 注册原生事件

  1. 调用 react-dom 模块中的 render 时,若第一次挂载则递归依次调用 legacyCreateRootFromDOMContainercreateLegacyRoot 创建容器对象 ReactDOMLegacyRoot 的实例

  2. ReactDOMLegacyRoot 递归依次调用 createRootImplcreateContainer 创建了组件挂载的根元素,并表及为根元素

  3. 调用 listenToAllSupportedEvents 在根元素上挂载事件,若当前容器为注释元素,则取当前元素的父元素为事件挂载的节点

  4. listenToAllSupportedEvents 中遍历初始化时注册的 allNativeEvents 事件列表注册事件,判断根元素是否为 documet 节点,不是则获取 document 节点,在 document 节点节点上注册 selectionchange 事件

    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
    // file: packages/react-dom/src/events/DOMPluginEventSystem.js
    const listeningMarker = '_reactListening' +Math.random().toString(36).slice(2);
    export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
    if (!(rootContainerElement: any)[listeningMarker]) {
    rootContainerElement[listeningMarker] = true
    allNativeEvents.forEach((domEventName) => {
    // We handle selectionchange separately because it
    // doesn't bubble and needs to be on the document.
    if (domEventName !== 'selectionchange') {
    if (!nonDelegatedEvents.has(domEventName)) {
    listenToNativeEvent(domEventName, false, rootContainerElement)
    }
    listenToNativeEvent(domEventName, true, rootContainerElement)
    }
    })
    const ownerDocument =
    rootContainerElement.nodeType === DOCUMENT_NODE
    ? rootContainerElement
    : (rootContainerElement).ownerDocument
    if (ownerDocument !== null) {
    // The selectionchange event also needs deduplication
    // but it is attached to the document.
    if (!(ownerDocument)[listeningMarker]) {
    ownerDocument[listeningMarker] = true
    listenToNativeEvent('selectionchange', false, ownerDocument)
    }
    }
    }
    }
  5. createEventListenerWrapperWithPriority 函数先获取事件优先级,再根据优先级确定构造监听器的函数

    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
    // file: packages/react-dom/src/events/ReactDOMEventListener.js
    export function createEventListenerWrapperWithPriority(
    targetContainer: EventTarget,
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags
    ): Function {
    const eventPriority = getEventPriority(domEventName)
    let listenerWrapper
    switch (eventPriority) {
    case DiscreteEventPriority:
    listenerWrapper = dispatchDiscreteEvent
    break
    case ContinuousEventPriority:
    listenerWrapper = dispatchContinuousEvent
    break
    case DefaultEventPriority:
    default:
    listenerWrapper = dispatchEvent
    break
    }
    return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer
    )
    }
  6. 调用 createEventListenerWrapperWithPriority 创建监听函数,根据 passive 属性的支持和 capture 情况分别调用不同的事件监听方式

    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
    // file: packages/react-dom/src/events/DOMPluginEventSystem.js
    export function listenToNativeEvent(
    domEventName: DOMEventName,
    isCapturePhaseListener: boolean,
    target: EventTarget
    ): void {
    let eventSystemFlags = 0
    if (isCapturePhaseListener) {
    eventSystemFlags |= IS_CAPTURE_PHASE
    }
    addTrappedEventListener(
    target,
    domEventName,
    eventSystemFlags,
    isCapturePhaseListener
    )
    }

    function addTrappedEventListener(
    targetContainer: EventTarget,
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags,
    isCapturePhaseListener: boolean,
    isDeferredListenerForLegacyFBSupport?: boolean
    ) {
    let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags
    )
    // If passive option is not supported, then the event will be
    // active and not passive.
    let isPassiveListener = undefined
    if (passiveBrowserEventsSupported) {
    // 是否支持 passive 属性, https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
    if (
    domEventName === 'touchstart' ||
    domEventName === 'touchmove' ||
    domEventName === 'wheel'
    ) {
    isPassiveListener = true
    }
    }

    targetContainer =
    enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
    ? (targetContainer: any).ownerDocument
    : targetContainer

    let unsubscribeListener
    // TODO: There are too many combinations here. Consolidate them.
    if (isCapturePhaseListener) {
    if (isPassiveListener !== undefined) {
    unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
    targetContainer,
    domEventName,
    listener,
    isPassiveListener
    )
    } else {
    unsubscribeListener = addEventCaptureListener(
    targetContainer,
    domEventName,
    listener
    )
    }
    } else {
    if (isPassiveListener !== undefined) {
    unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
    targetContainer,
    domEventName,
    listener,
    isPassiveListener
    )
    } else {
    unsubscribeListener = addEventBubbleListener(
    targetContainer,
    domEventName,
    listener
    )
    }
    }
    }

事件触发

react 事件触发

  1. 有事件触发时,调用 events/ReactDOMEventListener 下的 dispatchEvent 触发事件

  2. 当事件不可重新触发时直接调用 attemptToDispatchEvent 进行事件的触发,否则事件放入队列等待调用

    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
    // file: packages/react-dom/src/events/ReactDOMEventListener.js
    export function dispatchEvent(
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags,
    targetContainer: EventTarget,
    nativeEvent: AnyNativeEvent
    ): void {
    const allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0

    if (
    allowReplay &&
    hasQueuedDiscreteEvents() &&
    isReplayableDiscreteEvent(domEventName)
    ) {
    //更新队列中就把事件放入更新队列
    queueDiscreteEvent(
    null, // Flags that we're not actually blocked on anything as far as we know.
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent
    )
    return
    }

    const blockedOn = attemptToDispatchEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent
    )

    if (blockedOn === null) {
    // 触发事件成功
    if (allowReplay) {
    clearIfContinuousEvent(domEventName, nativeEvent)
    }
    return
    }

    // 其它逻辑
    }
  3. 获取触发事件的元素,然后获取最近的虚拟 dom 节点的实例(向上查找,注:dom 上存储了虚拟 dom 的引用,快速查找),通过虚拟 dom 节点获取最近挂载的 fiber 节点(向上查找)

    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
    // file: packages/react-dom/src/events/ReactDOMEventListener.js
    // Attempt dispatching an event. Returns a SuspenseInstance or Container if it's blocked.
    export function attemptToDispatchEvent(
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags,
    targetContainer: EventTarget,
    nativeEvent: AnyNativeEvent
    ): null | Container | SuspenseInstance {
    const nativeEventTarget = getEventTarget(nativeEvent)
    let targetInst = getClosestInstanceFromNode(nativeEventTarget)

    if (targetInst !== null) {
    const nearestMounted = getNearestMountedFiber(targetInst)
    if (nearestMounted === null) {
    // 树已经被销毁,触发对象置空
    targetInst = null
    } else {
    // 其它逻辑
    }
    }
    dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    targetInst,
    targetContainer
    )
    // We're not blocked on anything.
    return null
    }
  4. attemptToDispatchEvent 调用 dispatchEventForPluginEventSystem,找出触发事件元素的根节点实例,然后通过 dispatchEventsForPlugins 批量更新

    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
    // file: packages/react-dom/src/events/DOMPluginEventSystem.js
    export function dispatchEventForPluginEventSystem(
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags,
    nativeEvent: AnyNativeEvent,
    targetInst: null | Fiber,
    targetContainer: EventTarget
    ): void {
    let ancestorInst = targetInst
    if (
    (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
    (eventSystemFlags & IS_NON_DELEGATED) === 0
    ) {
    const targetContainerNode = ((targetContainer: any): Node)

    if (targetInst !== null) {
    // The below logic attempts to work out if we need to change
    // the target fiber to a different ancestor. We had similar logic
    // in the legacy event system, except the big difference between
    // systems is that the modern event system now has an event listener
    // attached to each React Root and React Portal Root. Together,
    // the DOM nodes representing these roots are the "rootContainer".
    // To figure out which ancestor instance we should use, we traverse
    // up the fiber tree from the target instance and attempt to find
    // root boundaries that match that of our current "rootContainer".
    // If we find that "rootContainer", we find the parent fiber
    // sub-tree for that root and make that our ancestor instance.
    let node = targetInst

    mainLoop: while (true) {
    if (node === null) {
    return
    }
    const nodeTag = node.tag
    if (nodeTag === HostRoot || nodeTag === HostPortal) {
    let container = node.stateNode.containerInfo
    if (isMatchingRootContainer(container, targetContainerNode)) {
    break
    }
    if (nodeTag === HostPortal) {
    // The target is a portal, but it's not the rootContainer we're looking for.
    // Normally portals handle their own events all the way down to the root.
    // So we should be able to stop now. However, we don't know if this portal
    // was part of *our* root.
    let grandNode = node.return
    while (grandNode !== null) {
    const grandTag = grandNode.tag
    if (grandTag === HostRoot || grandTag === HostPortal) {
    const grandContainer = grandNode.stateNode.containerInfo
    if (
    isMatchingRootContainer(
    grandContainer,
    targetContainerNode
    )
    ) {
    // This is the rootContainer we're looking for and we found it as
    // a parent of the Portal. That means we can ignore it because the
    // Portal will bubble through to us.
    return
    }
    }
    grandNode = grandNode.return
    }
    }
    // Now we need to find it's corresponding host fiber in the other
    // tree. To do this we can use getClosestInstanceFromNode, but we
    // need to validate that the fiber is a host instance, otherwise
    // we need to traverse up through the DOM till we find the correct
    // node that is from the other tree.
    while (container !== null) {
    const parentNode = getClosestInstanceFromNode(container)
    if (parentNode === null) {
    return
    }
    const parentTag = parentNode.tag
    if (parentTag === HostComponent || parentTag === HostText) {
    node = ancestorInst = parentNode
    continue mainLoop
    }
    container = container.parentNode
    }
    }
    node = node.return
    }
    }
    }

    batchedEventUpdates(() =>
    dispatchEventsForPlugins(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    ancestorInst,
    targetContainer
    )
    )
    }
  5. dispatchEventsForPlugins 先获取当前 Fiber 上绑定的对应事件,然后 processDispatchQueue 通过按照顺序 processDispatchQueueItemsInOrder 处理各个事件

    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
    // file: packages/react-dom/src/events/DOMPluginEventSystem.js
    function dispatchEventsForPlugins(
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags,
    nativeEvent: AnyNativeEvent,
    targetInst: null | Fiber,
    targetContainer: EventTarget
    ): void {
    const nativeEventTarget = getEventTarget(nativeEvent)
    const dispatchQueue: DispatchQueue = []
    extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer
    )
    processDispatchQueue(dispatchQueue, eventSystemFlags)
    }

    function extractEvents(
    dispatchQueue: DispatchQueue,
    domEventName: DOMEventName,
    targetInst: null | Fiber,
    nativeEvent: AnyNativeEvent,
    nativeEventTarget: null | EventTarget,
    eventSystemFlags: EventSystemFlags,
    targetContainer: EventTarget
    ) {
    SimpleEventPlugin.extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer
    )
    // 其它逻辑
    }

    export function processDispatchQueue(
    dispatchQueue: DispatchQueue,
    eventSystemFlags: EventSystemFlags
    ): void {
    const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0
    for (let i = 0; i < dispatchQueue.length; i++) {
    // 取出事件监听函数执行
    const { event, listeners } = dispatchQueue[i]
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase)
    }
    }

    function processDispatchQueueItemsInOrder(
    event: ReactSyntheticEvent,
    dispatchListeners: Array<DispatchListener>,
    inCapturePhase: boolean
    ): void {
    let previousInstance
    if (inCapturePhase) {
    // 捕获事件从后往前执行
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
    const { instance, currentTarget, listener } = dispatchListeners[i]
    if (instance !== previousInstance && event.isPropagationStopped()) {
    return
    }
    executeDispatch(event, listener, currentTarget)
    previousInstance = instance
    }
    } else {
    // 冒泡事件从前往后执行
    for (let i = 0; i < dispatchListeners.length; i++) {
    const { instance, currentTarget, listener } = dispatchListeners[i]
    if (instance !== previousInstance && event.isPropagationStopped()) {
    return
    }
    executeDispatch(event, listener, currentTarget)
    previousInstance = instance
    }
    }
    }
  6. extractEvents 提取事件监听函数

    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
    // file: packages/react-dom/src/events/plugins/SimpleEventPlugin.js
    function extractEvents(
    dispatchQueue: DispatchQueue,
    domEventName: DOMEventName,
    targetInst: null | Fiber,
    nativeEvent: AnyNativeEvent,
    nativeEventTarget: null | EventTarget,
    eventSystemFlags: EventSystemFlags,
    targetContainer: EventTarget
    ): void {
    const reactName = topLevelEventsToReactNames.get(domEventName)
    if (reactName === undefined) {
    return
    }
    let SyntheticEventCtor = SyntheticEvent
    let reactEventType: string = domEventName

    // 根据事件类型获取对应的构造函数

    const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0
    if (
    enableCreateEventHandleAPI &&
    eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE
    ) {
    const listeners = accumulateEventHandleNonManagedNodeListeners(
    // TODO: this cast may not make sense for events like
    // "focus" where React listens to e.g. "focusin".
    ((reactEventType: any): DOMEventName),
    targetContainer,
    inCapturePhase
    )
    if (listeners.length > 0) {
    // Intentionally create event lazily.
    const event = new SyntheticEventCtor(
    reactName,
    reactEventType,
    null,
    nativeEvent,
    nativeEventTarget
    )
    dispatchQueue.push({ event, listeners })
    }
    } else {
    // Some events don't bubble in the browser.
    // In the past, React has always bubbled them, but this can be surprising.
    // We're going to try aligning closer to the browser behavior by not bubbling
    // them in React either. We'll start by not bubbling onScroll, and then expand.
    const accumulateTargetOnly =
    !inCapturePhase &&
    // TODO: ideally, we'd eventually add all events from
    // nonDelegatedEvents list in DOMPluginEventSystem.
    // Then we can remove this special list.
    // This is a breaking change that can wait until React 18.
    domEventName === 'scroll'

    const listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly,
    nativeEvent
    )
    if (listeners.length > 0) {
    // Intentionally create event lazily.
    const event = new SyntheticEventCtor(
    reactName,
    reactEventType,
    null,
    nativeEvent,
    nativeEventTarget
    )
    dispatchQueue.push({ event, listeners })
    }
    }
    }
  7. 调用 accumulateSinglePhaseListeners 获取事件监听列表,从触发元素递归向上查询注册的事件然后放入监听器列表中返回

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
// file: packages/react-dom/src/events/DOMPluginEventSystem.js
export function accumulateSinglePhaseListeners(
targetFiber: Fiber | null,
reactName: string | null,
nativeEventType: string,
inCapturePhase: boolean,
accumulateTargetOnly: boolean,
nativeEvent: AnyNativeEvent
): Array<DispatchListener> {
const captureName = reactName !== null ? reactName + 'Capture' : null
const reactEventName = inCapturePhase ? captureName : reactName
let listeners: Array<DispatchListener> = []

let instance = targetFiber
let lastHostComponent = null

// Accumulate all instances and listeners via the target -> root path.
while (instance !== null) {
const { stateNode, tag } = instance
// Handle listeners that are on HostComponents (i.e. <div>)
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode

// createEventHandle listeners
if (enableCreateEventHandleAPI) {
const eventHandlerListeners =
getEventHandlerListeners(lastHostComponent)
if (eventHandlerListeners !== null) {
eventHandlerListeners.forEach((entry) => {
if (
entry.type === nativeEventType &&
entry.capture === inCapturePhase
) {
listeners.push(
createDispatchListener(
instance,
entry.callback,
(lastHostComponent: any)
)
)
}
})
}
}

// Standard React on* listeners, i.e. onClick or onClickCapture
if (reactEventName !== null) {
const listener = getListener(instance, reactEventName)
if (listener != null) {
listeners.push(
createDispatchListener(instance, listener, lastHostComponent)
)
}
}
}
instance = instance.return
}
return listeners
}

react中的jsx语法

JSX

JSX 是一种嵌入式的类似 XML 的语法。 它可以被转换成合法的 JavaScript,尽管转换的语义是依据不同的实现而定的。 JSXReact 框架而流行,但也存在其它的实现。

React 中的 JSX

JSX 为我们提供了创建 React 元素方法(React.createElement(component, props, ...children))的语法糖(syntactic sugar

1
2
3
const element = <div>Hello, world!</div>
// 编译后会被转化为
var element = React.createElement('div', null, 'Hello, world!') // 返回一个对象

JSX 代表 JS 对象

JSX 本身也是一个表达式,在编译后,JSX 表达式会变成普通的 JavaScript 对象。执行 React.createElement 之后最终会执行一下代码生成一个普通的 JavaScript 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ReactElement = function (type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,

// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,

// Record the component responsible for creating this element.
_owner: owner,
}

return element
}

由上可以看出,执行 React.createElement 时只支持表达式,同时可以在以下情况中使用

  • 可以在 if 语句或 for 循环中使用 JSX
  • 可以将它赋值给变量
  • 可以将它作为参数接收
  • 可以在函数中返回 JSX
表达式

JavaScript 中,表达式就是一个短语,Javascript 解释器会将其计算出一个结果,常量就是最简单的一类表达式。常用的表达式有:

  • 变量名
  • 函数定义表达式
  • 属性访问表达式
  • 函数调用表达式
  • 算数表达式
  • 关系表达式
  • 逻辑表达式

注意: if 语句以及 for 循环不是 JavaScript 表达式

JSX 可自动防范注入攻击

在默认情况下,React DOM 会将所有嵌入 JSX 的值进行编码,利用 createTextNode 进行 HTML 转义

1
2
3
4
5
export function createTextNode(text, rootContainerElement) {
return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
text
)
}

若希望直接将字符串不经转义编码直接插入到 HTML 文档流,可以使用 dangerouslySetInnerHTML 属性,这是一个 React 版的 innerHTML,该属性接收一个 key__html 的对象

注意

  • JSX 会自动删除一行中开头和结尾处的空白符;
  • JSX 会自动删除空行;
  • JSX 会删除紧邻标签的换行;
  • JSX 会删除字符串中的换行;
  • 字符串中的换行会被转换成一个空格。

优点

  • 允许使用熟悉的语法来定义 HTML 元素树
  • 提供了更加语义化且易懂的标签
  • 程序结构更容易被直观化
  • 抽象了 React Element 的创建过程
  • 可以随时掌控 HTML 标签以及生成这些标签的代码
  • 原生 Javascript

参考