内部实例的伪实现(思考)

为什么需要内部实例呢?
React的主要特性是你可以重新渲染任何东西,并且你不会重复创建DOM以及重置state.

1
2
3
ReactDOM.render(<App />, rootEl);
//下面这一行会重新使用已存在的DOM
ReactDOM.render(<App />, rootEl);

当组件需要更新时,如何存储必要的信息呢?比如说publicInstances,DOM元素与组件之间的关系。
堆栈协调器代码库通过在类中加了一个mount()函数解决这个问题,这种方式有点缺点,正在改进。
mountHost()mountComposite()分离出来的替代方案是创建了两个类DOMComponentCompositeComponent
这两个类都有一个接收element的构造函数,当然还有一个mount()方法返回已镶嵌的节点,用一个工厂方法实例化正确的
class来替代顶级mount()方法。

1
2
3
4
5
6
7
8
function instantiateComponent(element) {
var type = element.type;
if(typeof type === 'function') {
return new CompositeComponent(type);
}else if(typeof type === 'string') {
return new DOMComponent(type);
}
}

CompositeComponent(复杂组件)的实现:

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
class CompositeComponent {
constructor(element) {
this.currentElement = element;
this.renderedComponent = null;
this.publicInstance = null;
}

getPublicInstance() {
// For composite components, expose the class instance.
return this.publicInstance;
}

mount() {
var element = this.currentElement;
var type = element.type;
var props = element.props;

var publicInstance;
var renderedElement;
if (isClass(type)) {
// Component class
publicInstance = new type(props);
// Set the props
publicInstance.props = props;
// Call the lifecycle if necessary
if (publicInstance.componentWillMount) {
publicInstance.componentWillMount();
}
renderedElement = publicInstance.render();
} else if (typeof type === 'function') {
// Component function
publicInstance = null;
renderedElement = type(props);
}

// Save the public instance
this.publicInstance = publicInstance;

// Instantiate the child internal instance according to the element.
// It would be a DOMComponent for <div /> or <p />,
// and a CompositeComponent for <App /> or <Button />:
var renderedComponent = instantiateComponent(renderedElement);
this.renderedComponent = renderedComponent;

// Mount the rendered output
return renderedComponent.mount();
}
}

DOMComponent类的实现:

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
class DOMComponent {
constructor(element) {
this.currentElement = element;
this.renderedChildren = [];
this.node = null;
}

getPublicInstance() {
// For DOM components, only expose the DOM node.
return this.node;
}

mount() {
var element = this.currentElement;
var type = element.type;
var props = element.props;
var children = props.children || [];
if (!Array.isArray(children)) {
children = [children];
}

// Create and save the node
var node = document.createElement(type);
this.node = node;

// Set the attributes
Object.keys(props).forEach(propName => {
if (propName !== 'children') {
node.setAttribute(propName, props[propName]);
}
});

// Create and save the contained children.
// Each of them can be a DOMComponent or a CompositeComponent,
// depending on whether the element type is a string or a function.
var renderedChildren = children.map(instantiateComponent);
this.renderedChildren = renderedChildren;

// Collect DOM nodes they return on mount
var childNodes = renderedChildren.map(child => child.mount());
childNodes.forEach(childNode => node.appendChild(childNode));

// Return the DOM node as mount result
return node;
}
}

结果是,每一个内部实例都不论是复合型的还是平台特有的,都指向他的child的内部实例,比如:
<App>组件渲染了一个<Button>,<Button>渲染了一个<div>,内部实例树将是下面的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
[object CompositeComponent] {
currentElement: <App />,
publicInstance: null,
renderedComponent: [object CompositeComponent] {
currentElement: <Button />,
publicInstance: [object Button],
renderedComponent: [object DOMComponent] {
currentElement: <div />,
node: [object HTMLDivElement],
renderedChildren: []
}
}
}

在DOM中只看见<div>,但是内部实例可以包含混合型及平台特有的内部实例二者。混合型内部实例
需要存储:

  • 当前元素
  • 如果元素类型是类,则包含公共实例
  • 单独的渲染的内部实例,既可以是DOMComponent,也可以是CompositeComponent.
    平台特有内部实例需要存储:
  • 当前元素
  • DOM节点
  • 所有的子内部实例,它们既可以是DOMComponent也可以是CompositeComponent.