React源码解读(一) ——— ReactDom.render()

React源码解读(一)

前言

这两周面试了几个人,虽然是一面,问的不深,但是给我的感觉都是对React了解的比较浅,稍微往深一点问基本都是回答得很宽泛,答不到点子上,虽然有引导他们去思考但是由于react的理解很片面所以基本也答不出来,因此就来写个我对于react的源码的解读,我将从项目结构入手分析React源码。

ReactDOM.render() (github上ReactDOM.js 672行)

一个React项目的最外层就是一个指定的DOM节点,通过render函数将我们需要渲染的组件插入到指定DOM节点中来实现视图,来看看render到底做了什么。
源码如下:

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
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
return legacyRenderSubtreeIntoContainer( //legacyRenderSubtreeIntoContainer 遗留渲染子树到容器(这名字我只能这么白话翻译了,读不懂)
null,
element,
container,
false,
callback,
);
},

function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>, //父组件
children: ReactNodeList, //子组件,ReactElement
container: DOMContainer, //DOM容器
forceHydrate: boolean, //强制注水(用于ssr,寓意使干巴巴的数据变成水灵灵的HTML)
callback: ?Function, //回调
) {
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
let root: Root = (container._reactRootContainer: any);
if (!root) { //不存在container._reactRootContainer时,及第一次渲染
// Initial mount //初始化React根节点(虚拟DOM)
root = container._reactRootContainer = legacyCreateRootFromDOMContainer( //其实是个ReactRoot对象
container,
forceHydrate,
);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback); //root对象会去走这个render方法
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
return getPublicRootInstance(root._internalRoot);
}

由于render方法在React生命周期各个时期中有着不同的作用,因此render中分了首次挂载和更新两种情况。

首次挂载时,通过legacyCreateRootFromDOMContainer生成了一个root对象,并将这个root对象赋给了DOM容器,下面来看看legacyCreateRootFromDOMContainer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
container.removeChild(rootSibling);
}
}
// Legacy roots are not async by default.
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}

可以看出来它干了两件事情,一个是当不应该注水的时候清空了container的子节点(应该就是非ssr的情况下),还有一件事情就是返回了一个新的ReactRoot对象。问题又来了这个ReactRoot又是个什么东西。

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
function ReactRoot(
container: DOMContainer,
isConcurrent: boolean,
hydrate: boolean,
) {
const root = createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}

ReactRoot.prototype.render = function( //渲染
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (callback !== null) {
work.then(callback);
}
updateContainer(children, root, null, work._onCommit); //最终会执行updateContainer方法并返回一个ReactWork对象
return work;
};

ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (callback !== null) {
work.then(callback);
}
updateContainer(children, root, parentComponent, work._onCommit);
return work;
};

...

从上面可以看出来这个ReactRoot是个构造函数,它有render等方法,上述的root则会去执行render方法并返回一个ReactWork对象,那接下来就是这个updateContainer到底干了些什么了。通过上面的层层扒皮,无论怎样判断,最终都会到render方法和legacy_renderSubtreeIntoContainer方法中,而这两个方法的核心就是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
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;

const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}

return scheduleRootUpdate(current, element, expirationTime, callback); //根更新计划表
}

function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {

const update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};

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

flushPassiveEffects();
enqueueUpdate(current, update);
scheduleWork(current, expirationTime);

return expirationTime;
}

updateContainer传入的参数有虚拟dom对象树、之前造出来的root中和fiber相关的_internalRoot、父组件、回调函数。

觉得不错的话可以打赏哦