Hello World

下面的示例都是基于webpack的
react: 16.9.0
react-dom: 16.9.0

学习一个框架先从最简单的示例做起,比如下面的示例:

1
2
3
4
5
6
import React from 'react';
import { render } from 'react-dom';
render(
<p>test</p>,
document.getElementById('root')
);

JSX

  1. 如果我们使用webpack来开发react应用,如何需要使用JSX,我们需要引入以下代码:

    1
    import React from 'react';
  2. 如果你引入了这段代码,babel会自动将JSX解析成如下形式:

    1
    2
    3
    4
    5
    React.createElement(
    'p',
    null, // 这个参数代表JSX的props
    'test'
    )
  3. 所以说需要导入React来支持JSX的解析。

  4. 具体babel是怎么编译的,查看这个在线演示

研究重点

至此,我们发现两个需要研究的重点

  1. 第一个是react-dom中的render方法。
  2. 第二个是react中的createElement方法。

下面我们来一个个看:

准备工作

  1. clone一下react的仓库。
  2. 源码是使用flow写的,最好build一下,拿最后生成的es5文件来研究源码,flow版本有一些webpack的变量和参数,很难找,不清晰,而且依赖引用太多,跳跃性太强。

react/createElement

  1. 我们只看这个示例执行的时候createElement跑过的分支,整理如下:
    1
    2
    3
    function createElementWithValidation(type = 'p', props = null, children = 'test') {
    return createElement.apply(this, arguments);
    }

如果有多个children,会使用React.createElement层层嵌套:如<p><a>test</a></p>,会被解析成:React.createElement('p', null, React.createElement('a', null, 'test'))

  1. createElement:为了方便查看,直接将参数携带过来
1
2
3
4
5
6
7
8
9
10
function createElement(type = 'p', config = null, children = 'test') {
var propName;
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
props.children = children;
return ReactElement(type, key, ref, self, source, null, props);
}
  1. 很简单,最后会返回这样的执行函数:
    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
    var ReactElement = function (
    type = 'p',
    key = null,
    ref = null,
    self = null,
    source = null,
    owner = null,
    props = { children: 'test' }
    ) {
    const element = {
    $$typeof: Symbol.for('react.element'),
    type: 'p',
    key: null,
    ref: null,
    props: { children: 'test' },
    _owner: null,
    _store: {
    validated: false
    },
    _self: null,
    _source: null,
    };
    // 这里有个小细节,freeze方法相当于浅冻结,所以需要对属性中的对象再次冻结
    if (Object.freeze) {
    Object.freeze(element.props);
    Object.freeze(element);
    }
    return element;
    };

react-dom/render

现在我们知道其实最后执行的代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ReactDOM.render(
{
$$typeof: Symbol.for('react.element'),
type: 'p',
key: null,
ref: null,
props: { children: 'test' },
_owner: null,
_store: {
validated: false
},
_self: null,
_source: null,
},
document.getElementById('root')
);

源码

  1. 话不多说:找到build/node_modules/react-dom/cjs/react-dom.development.js

render

1
2
3
function (element, container, callback) {
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}

legacyRenderSubtreeIntoContainer

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
function legacyRenderSubtreeIntoContainer(
parentComponent = null,
children = {
$$typeof: Symbol.for('react.element'),
type: 'p',
key: null,
ref: null,
props: { children: 'test' },
_owner: null,
_store: {
validated: false
},
_self: null,
_source: null,
},
container = AppContainerDom, // 就是承载web网站dom容器
forceHydrate = false,
callback = undefined) {
const rootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
var root = rootContainer;
container._reactRootContainer = rootContainer;
var fiberRoot = root._internalRoot;
unbatchedUpdates(function () {
updateContainer(children, fiberRoot, parentComponent, callback);
});
return getPublicRootInstance(fiberRoot);
}

拆分一下

  1. legacyCreateRootFromDOMContainer
  2. fiberRoot,也就是root._internalRoot,也就是rootContainer._internalRoot,也就是legacyCreateRootFromDOMContainer执行过后返回的对象里面的那个_internalRoot属性
  3. updateContainer
  4. unbatchedUpdates
  5. getPublicRootInstance
  6. ok,一个个来。

legacyCreateRootFromDOMContainer

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
// 第一步:格式化了一下`AppContainerDom`,移除了一些不必要的`dom`节点
function legacyCreateRootFromDOMContainer(container = AppContainerDom, forceHydrate = false) {
var rootSibling;
// 移除app容器的所有dom节点
while (rootSibling = container.lastChild) {
container.removeChild(rootSibling);
}
return new ReactSyncRoot(container, LegacyRoot, undefined);
}
// 第二步:通过createContainer生成了root,赋值给_internalRoot
function ReactSyncRoot(container = FormatedAppContainerDom, tag = 0, options = undefined) {
var hydrate = false;
var hydrationCallbacks = null;
var root = createContainer(container, tag, hydrate, hydrationCallbacks);
this._internalRoot = root;
}
// 第三步:这个地方估计是v16之前用的函数,后来替换成fiber形式的了
function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}

// 第四步
function createFiberRoot(containerInfo = FormatedAppContainerDom, tag = 0, hydrate = false, hydrationCallbacks = null) {
var root = new FiberRootNode(containerInfo, tag, hydrate);
var uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
return root;
}
// 第五步:生成root
{
// FiberRootNode部分
function FiberRootNode(containerInfo = FormatedAppContainerDom, tag = 0, hydrate = false) {
this.tag = 0;
this.current = null;
this.containerInfo = FormatedAppContainerDom;
this.pendingChildren = null;
this.pingCache = null;
this.finishedExpirationTime = 0;
this.finishedWork = null;
this.timeoutHandle = -1;
this.context = null;
this.pendingContext = null;
this.hydrate = false;
this.firstBatch = null;
this.callbackNode = null;
this.callbackExpirationTime = 0;
this.firstPendingTime = 0;
this.lastPendingTime = 0;
this.pingTime = 0;
this.interactionThreadID = 1, // require('scheduler/tracing').unstable_getThreadID(); 这个就是一个递增ID的函数
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
// createHostRootFiber部分
function createHostRootFiber(tag = 0) {
return createFiber(3, null, null, 0);
}
var createFiber = function (tag, pendingProps, key, mode) {
return new FiberNode(tag, pendingProps, key, mode);
};
function FiberNode(tag = 3, pendingProps = null, key = null, mode = 0) {
this.tag = 3;
this.key = null;
this.elementType = null;
this.type = null;
this.stateNode = null;

this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = null;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = 0;

this.effectTag = 0;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.expirationTime = 0;
this.childExpirationTime = 0;
this.alternate = null;

this.actualDuration = Number.NaN;
this.actualStartTime = Number.NaN;
this.selfBaseDuration = Number.NaN;
this.treeBaseDuration = Number.NaN;

this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;

this._debugID = 2;
this._debugIsCurrentlyTiming = false;

this._debugSource = null;
this._debugOwner = null;
this._debugNeedsRemount = false;
this._debugHookTypes = null;
}
}

// 第六步:root的结构(结合createFiberRoot和FiberRootNode)
const ROOTINSTANCE = {
tag: 0,
current: null,
containerInfo: FormatedAppContainerDom,
pendingChildren: null,
pingCache: null,
finishedExpirationTime: 0,
finishedWork: null,
timeoutHandle: -1,
context: null,
pendingContext: null,
hydrate: false,
firstBatch: null,
callbackNode: null,
callbackExpirationTime: 0,
firstPendingTime: 0,
lastPendingTime: 0,
pingTime: 0,
interactionThreadID: 1,
memoizedInteractions: new Set(),
pendingInteractionMap: new Map(),

current: {
tag: 3,
key: null,
elementType: null,
type: null,
stateNode: null,

return: null,
child: null,
sibling: null,
index: 0,
ref: null,
pendingProps: null,
memoizedProps: null,
updateQueue: null,
memoizedState: null,
dependencies: null,
mode: 0,

effectTag: 0,
nextEffect: null,
firstEffect: null,
lastEffect: null,
expirationTime: 0,
childExpirationTime: 0,
alternate: null,

actualDuration: Number.NaN,
actualStartTime: Number.NaN,
treeBaseDuration: Number.NaN,

actualDuration: 0,
actualStartTime: -1,
selfBaseDuration: 0,
treeBaseDuration: 0,

_debugID: 2,
_debugIsCurrentlyTiming: false,

_debugSource: null,
_debugOwner: null,
_debugNeedsRemount: false,
_debugHookTypes: null,

stateNode: ROOT_SELF, // root本身的引用
},
}

结果

通过上面的步骤可以看到,最终legacyCreateRootFromDOMContainer返回的是一个root的实例,ROOTINSTANCE就是legacyRenderSubtreeIntoContainer中的fiberRoot

1
2
3
{
_internalRoot: ROOTINSTANCE
}

updateContainer

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
function updateContainer(
element = {
$$typeof: Symbol.for('react.element'),
type: 'p',
key: null,
ref: null,
props: { children: 'test' },
_owner: null,
_store: {
validated: false
},
_self: null,
_source: null,
},
container = ROOTINSTANCE,
parentComponent = null,
callback = undefined
) {
var current$$1 = container.current;
// 当前时间,todo:这玩意是怎么计算出来的以及它的作用
var currentTime = msToExpirationTime(now());
// 不确定的配置
var suspenseConfig = requestCurrentSuspenseConfig();
// 终止时间,todo:这玩意是怎么计算出来的以及它的作用
var expirationTime = computeExpirationForFiber(currentTime, current$$1, suspenseConfig);
return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, suspenseConfig, callback);
}
// updateContainerAtExpirationTime
function updateContainerAtExpirationTime(
element = {
$$typeof: Symbol.for('react.element'),
type: 'p',
key: null,
ref: null,
props: { children: 'test' },
_owner: null,
_store: {
validated: false
},
_self: null,
_source: null,
},
container = ROOTINSTANCE,
parentComponent = null,
expirationTime,
suspenseConfig,
callback = undefined
) {
var current$$1 = container.current;
var context = {};
container.context = context;
return scheduleRootUpdate(current$$1, element, expirationTime, suspenseConfig, callback);
}
{
// scheduleRootUpdate
function scheduleRootUpdate(
current$$1 = ROOTINSTANCE.current,
element = {
$$typeof: Symbol.for('react.element'),
type: 'p',
key: null,
ref: null,
props: { children: 'test' },
_owner: null,
_store: {
validated: false
},
_self: null,
_source: null,
},
expirationTime,
suspenseConfig,
callback = undefined
) {
var update = createUpdate(expirationTime, suspenseConfig);
update.payload = {
element: element
};
callback = null;
enqueueUpdate(current$$1, update);
scheduleWork(current$$1, expirationTime);
return expirationTime;
}
// createUpdate
function createUpdate(expirationTime, suspenseConfig) {
var update = {
expirationTime: expirationTime,
suspenseConfig: suspenseConfig,
tag: 0,
payload: null,
callback: null,
next: null,
nextEffect: null
};
{
// todo:这个优先级是怎么计算的
update.priority = getCurrentPriorityLevel();
}
return update;
}
// enqueueUpdate: 排队更新
function enqueueUpdate(
fiber = ROOTINSTANCE.current,
update = {
expirationTime: expirationTime,
suspenseConfig: suspenseConfig,
tag: 0,
payload: null,
callback: null,
next: null,
nextEffect: null,
priority: getCurrentPriorityLevel(),
payload: {
element: {
$$typeof: Symbol.for('react.element'),
type: 'p',
key: null,
ref: null,
props: { children: 'test' },
_owner: null,
_store: {
validated: false
},
_self: null,
_source: null,
}
}
}
) {
// Update queues are created lazily.
var alternate = null;
var queue1;
var queue2;

// There's only one fiber.
queue1 = fiber.updateQueue = {
baseState: null,
firstUpdate: null,
lastUpdate: null,
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null
};
queue2 = null;
// 就是更新了一下fiber的updateQueue
queue1.firstUpdate = queue1.lastUpdate = update;
}
}
// 可以看到,经过了enqueueUpdate之后,ROOTINSTANCE.current发生了一些变化,主要是updateQueue发生了变化,这个变化也会同步到ROOTINSTANCE,应为它们都是一个引用,而且没有被深拷贝
// 变化之后的ROOTINSTANCE.current
const enqueueUpdate_ROOTINSTANCE_current = {
tag: 3,
key: null,
elementType: null,
type: null,
stateNode: null,

return: null,
child: null,
sibling: null,
index: 0,
ref: null,
pendingProps: null,
memoizedProps: null,
updateQueue: {
baseState: null,
firstUpdate: {
expirationTime: expirationTime,
suspenseConfig: suspenseConfig,
tag: 0,
payload: null,
callback: null,
next: null,
nextEffect: null,
priority: getCurrentPriorityLevel(),
payload: {
element: {
$$typeof: Symbol.for('react.element'),
type: 'p',
key: null,
ref: null,
props: { children: 'test' },
_owner: null,
_store: {
validated: false
},
_self: null,
_source: null,
}
}
},
lastUpdate: {
expirationTime: expirationTime,
suspenseConfig: suspenseConfig,
tag: 0,
payload: null,
callback: null,
next: null,
nextEffect: null,
priority: getCurrentPriorityLevel(),
payload: {
element: {
$$typeof: Symbol.for('react.element'),
type: 'p',
key: null,
ref: null,
props: { children: 'test' },
_owner: null,
_store: {
validated: false
},
_self: null,
_source: null,
}
}
},
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null
},
memoizedState: null,
dependencies: null,
mode: 0,

effectTag: 0,
nextEffect: null,
firstEffect: null,
lastEffect: null,
expirationTime: 0,
childExpirationTime: 0,
alternate: null,

actualDuration: Number.NaN,
actualStartTime: Number.NaN,
treeBaseDuration: Number.NaN,

actualDuration: 0,
actualStartTime: -1,
selfBaseDuration: 0,
treeBaseDuration: 0,

_debugID: 2,
_debugIsCurrentlyTiming: false,

_debugSource: null,
_debugOwner: null,
_debugNeedsRemount: false,
_debugHookTypes: null,

stateNode: ROOT_SELF, // root本身的引用
}
{
// scheduleWork
function scheduleUpdateOnFiber(fiber = enqueueUpdate_ROOTINSTANCE_current, expirationTime) {
var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
root.pingTime = NoWork;
// 获取优先级
var priorityLevel = getCurrentPriorityLevel();

// Register pending interactions on the root to avoid losing traced interaction data.
schedulePendingInteractions(root, expirationTime);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
var callback = renderRoot(root, Sync, true);
while (callback !== null) {
callback = callback(true);
}
}
// markUpdateTimeFromFiberToRoot
function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
// Update the source fiber's expiration time
if (fiber.expirationTime < expirationTime) {
fiber.expirationTime = expirationTime;
}
var alternate = fiber.alternate;
var node = fiber.return;
var root = null;
root = fiber.stateNode;
if (root !== null) {
// Update the first and last pending expiration times in this root
var firstPendingTime = root.firstPendingTime;

if (expirationTime > firstPendingTime) {
root.firstPendingTime = expirationTime;
}

var lastPendingTime = root.lastPendingTime;

if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
root.lastPendingTime = expirationTime;
}
}

return root;
}
}