如何读源码

最近花了一段时间研究了React的源码,收获良多,特此记录。
如何去读源码?可能是很多小伙伴的疑问,我认为读源码必须要有一些前提:

  • 必须熟知基础知识,也就是说你必须要用过该框架。
  • 熟读相关文档。
  • 大体上了解该框架的功能,特性。

React框架我一直使用,相关文档也熟读。熟读文档的好处是你可以在读源码的过程中,验证各种特性和功能,加深你的理解。前提了解了,那具体的操作步骤呢?

  • 首先在网上找了一些React源码相关的文章读一读。有疑惑的话记录下来,在之后的读源码过程中反问自己。并且把写的好文章保存下来。等自己读过源码后,再做对比。
  • 写一个最简单的Demo。
  • 本地build。前端框架的话在html中引入umd bundle(development build),未压缩的。然后基于上面的Demo进行调试。具体如何操作步骤可自行Google。
  • 理清目录结构。

以上的步骤是我根据这篇文章记录下来的,大家可自行看这篇文章,文章写的极其好。
读源码就像是剥洋葱,需要从外到里。首先忽略细节,理解基本的框架架构,然后再一步一步吃透细节。众所周知,React16引入了Fiber架构。可Fiber具体是什么?我们要是上手直接看这部分相关的代码,可能只会越来越糊涂。所以我们从入口入手。
下面是我自己写的Demo,我会基于这个Demo聊一聊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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './App.css';


class Article extends Component {
constructor(props) {
super(props);
this.state = { likes: Math.ceil(Math.random() * 100) };
}
render() {
<li>
<button onClick={e => this.like()}>
{likes}
<b>❤️</b>
</button>
<a href={url}>{name}</a>
</li>
}
}


class App extends Component {
constructor() {
super();
}
render() {
return (
<div className="App">
<p className="App-intro">
The list of articles is as follows, Please click favorites and likes.
</p>
<ul>
{this.props.articles.map(article => {
return <Article name={article.name} url={article.url} />
} )}
</ul>
</div>
);
}
}

const articles = [
{ name: "输入框的一些知识", url: "https://chengwen-zheng.github.io/2020/04/29/%E8%BE%93%E5%85%A5%E6%A1%86%E7%9A%84%E4%B8%80%E4%BA%9B%E7%9F%A5%E8%AF%86/" },
{ name: "数据库初涉", url: "https://chengwen-zheng.github.io/2020/04/26/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%9D%E6%B6%89/" },
{ name: "redux源码解析", url: "https://chengwen-zheng.github.io/2020/04/26/redux%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/" }
];

ReactDOM.render(<App articles = {articles}/>, document.getElementById('root'));

明确React入口以及jsx

React代码经过Babel的编译转换,才可以在浏览器中使用。所以我们将该代码用Babel线上转换工具进行转换,得到如图所示代码。
经过Babel转换之后的代码

由图可知,React代码入口为_reactDom.default.render。并且组件变成了一个层层调用_react.default.createElement的函数,也就是我们经常说的编译jsx。那么这个函数究竟做了什么?找到该源码的位置。

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
export function createElement(type, config, children) {
let propName;

// Reserved names are extracted
const props = {};

let key = null;
let ref = null;
let self = null;
let source = null;

if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}

self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}

// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
}

// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// 返回ReactElement对象。
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}

可以看出React将所有jsx(也包括Dom元素)变成ReactElement。我们看看ReactElement源码。

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

ReactElement元素就很简单,是一个有$$typeof、type、key、ref、props属性的对象。

ReactDom.render

ReactDom利用render函数将ReactElement元素挂载在Dom元素上,也就是说React将渲染以及和Dom相关的工作抽出来交给了ReactDom、ReactNative等。这种分离也达到了跨终端渲染的效果。我们接下来看看ReactDom.render的源代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const ReactDOM: Object = {
createPortal,
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
// 初始化Container
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
//render不会复用节点,因为是前端渲染.
false,
callback
);
}
};

legacyRenderSubtreeIntoContainer函数的本质上初始化Container,往后面看。

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
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
// TODO: Ensure all entry points contain this check
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);

// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
// Initial mount
//创建一个ReactRoot,ReactRoot 是创建整个React应用的根对象
let root: Root = (container._reactRootContainer: any);
if (!root) {
// Initial mount
// legacyCreateRootFromDOMContainer创建ReactRoot,本质也是创建了FiberRoot
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
// Initial mount
// 根据fiberRoot获取公共Root实例
// 就是fiberRoot.current.child.stateNode
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
//通过该实例instance去调用originalCallback方法
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
//初始化安装不应该批量更新
DOMRenderer.unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
// children就是转化jsx生成ReactElement树。
root.render(children, callback);
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
return DOMRenderer.getPublicRootInstance(root._internalRoot);
}

因为是第一次更新,所以Root为null,进入Initial mount。首先向container(也就是document.getElementById(‘root’))挂载_reactRootContainer。进入legacyCreateRootFromDOMContainer,legacyCreateRootFromDOMContainer的作用是创建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
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content. 不需要整合已有节点。(也就是说没有服务端渲染的时候)
//render的forceHydrate是false,所以会调用shouldHydrateDueToLegacyHeuristic方法来判断是否是服务端渲染.
if (!shouldHydrate) {
//如果不是服务端渲染的话
let warned = false;
let rootSibling;
//循环删除container的子节点
//为什么要删除?因为React认为这些节点是不需要复用的
while ((rootSibling = container.lastChild)) {
container.removeChild(rootSibling);
}
}
// Legacy roots are not async by default.
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}

function ReactRoot(
container: Container,
isConcurrent: boolean,
hydrate: boolean,
) {
const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}

可以看出ReactRoot最终创建了Container。挂载在_internalRoot。并且ReactRoot原型链上有render、unmount、legacy_renderSubtreeIntoContainer、createBatch方法legacyRenderSubtreeIntoContainer方法最终调用了root.render(children, callback)方法。root就是ReactRoot。所以就是调用了ReactRoot原型链上的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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
// 这里是createContainer挂载上去的FiberRoot。
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;

if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(children, root, null, work._onCommit);
return work;
};

function updateContainer(
element: ReactNodeList,
//container就是FiberRoot
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
// FiberRoot的current是RootFiber
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) {
warningWithoutStack(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}

// 每执行一次update都会更新下updateEnqueue.(enqueueSetState,cReplaceState,enqueueForceUpdate)里面也都有这个方法
enqueueUpdate(current, update);
// 任务调度(最复杂)
scheduleWork(current, expirationTime);
return expirationTime;
}

legacyCreateRootFromDOMContainer创建了Container,后续更新操作在ReactRoot.render里面进行。在创建的过程中,计算出后续任务调度的优先级,方便以后调度。还有生成update,并更新updateQueue。scheduleWork是开始进行异步任务调度。