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
70render(
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 | function legacyCreateRootFromDOMContainer( |
可以看出来它干了两件事情,一个是当不应该注水的时候清空了container的子节点(应该就是非ssr的情况下),还有一件事情就是返回了一个新的ReactRoot对象。问题又来了这个ReactRoot又是个什么东西。
1 | function ReactRoot( |
从上面可以看出来这个ReactRoot是个构造函数,它有render等方法,上述的root则会去执行render方法并返回一个ReactWork对象,那接下来就是这个updateContainer到底干了些什么了。通过上面的层层扒皮,无论怎样判断,最终都会到render方法和legacy_renderSubtreeIntoContainer方法中,而这两个方法的核心就是updateContainer,无非就是传不传父组件而已。
1 | export function updateContainer( |
updateContainer传入的参数有虚拟dom对象树、之前造出来的root中和fiber相关的_internalRoot、父组件、回调函数。