Skip to content

Commit

Permalink
mounting contd
Browse files Browse the repository at this point in the history
  • Loading branch information
cyan33 committed Feb 27, 2018
1 parent 5b95d80 commit cc370a3
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 6 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Learn React Source Code

* [Day1 - Guidance](blog/guidance.md)
* [Day2 - Mounting, Part 1](blog/mounting.md)
* [Day2 - Mounting](blog/mounting.md)
* [Day3 - Mounting - Contd](blog/mounting-contd.md)

## What Dilithium Hasn't Covered

Expand Down Expand Up @@ -38,10 +39,10 @@ Open the `index.html` manually.

## Disclaimers

1. Most code of Dilithium you've seen in this repo is originally written by [@zpao](), but it's also slightly changed here.
1. Most code of Dilithium you've seen in this repo is originally written by [@zpao](), but it's also slightly changed here. I'll keep digging some of the listed features and adding blog and source code on top of the current codebase.

2. The diffing algorithm used in the Dilithium is the stack reconcilliation, not the new fiber architecture.

## Liscense

MIT@Chang
MIT[@Chang](github.com/cyan33)
3 changes: 3 additions & 0 deletions blog/guidance.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ Answer:
除此之外,如果你还没有信心直接开始阅读源码,建议(按次序)阅读以下四篇官方的 React Advanced Guide。对于理解 React 的架构和一些重要概念很有帮助。

[JSX in Depth](https://reactjs.org/docs/jsx-in-depth.html)

[Implementation Details](https://reactjs.org/docs/implementation-notes.html)

[Reconciliation](https://reactjs.org/docs/reconciliation.html)

[Codebase Overview](https://reactjs.org/docs/codebase-overview.html)

## Ask yourself Before Move On
Expand Down
177 changes: 177 additions & 0 deletions blog/mounting-contd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Mounting - Contd

在上一节中,我们了解到,React 所有的复合组件(包括 class component 和 functional component)的 mounting 全部 defer 到了 DOM Component 的 mounting 中。并且,在 DOM Component 中的 `mountComponent` 方法中,我们留下了两个问题。

```js
mountComponent() {
// create real dom nodes
const node = document.createElement(this._currentElement.type)
this._domNode = node

this._updateNodeProperties({}, this._currentElement.props)
this._createInitialDOMChildren(this._currentElement.props)

return node
}
```

第一,怎样将当前 element 的 `props` 属性映射到当前 DOM 节点的属性?

第二,React 是怎样递归 mount 子组件的?

本篇博客主要讲解这两个问题。

## `updateNodeProperties`

我们回顾一下 `props` 的来源和数据结构。首先,`props` 是从 JSX 中来的:

```jsx
<div
className="container"
style={{
color: 'red',
fontSize: '24px'
}}
>
Hello World
</div>
```

编译后的结果是:

```js
React.createElement(
'div',
{
className: 'container',
style: {
color: 'red',
fontSize: '24px'
}
},
'Hello World'
);
```

运行 `createElement` 后,最终返回值,也就是 Element,变成了这样的数据结构:

```js
{
type: 'div',
props: {
className: 'container',
children: 'Hello World',
style: {
color: 'red',
fontSize: '24px'
}
},
}
```

可以看出,`props` 就是一个**不完全的**和HTML属性之间的映射。为什么说是**不完全**呢?有以下两个原因:

1. 有些属性并不是 DOM 属性,也不会被挂载在 DOM 上。比如 `children`
2. `props` 的属性名和 HTML 的 property 并不存在一一对应的关系。比如说 `className` 对应的应该是 `class`

除此之外,我们还应该考虑很重要的一点,那就是当组件更新,`props.style` 中的更新方式应该是怎样的呢?(这一部分本应放在 `updating` 再讲,但是为了整个函数的连贯性,我们在此一并讲完。)举个例子:

当一个组件的 `style`

```js
{
fontSize: '36px'
}
```

变为

```js
{
color: 'red'
}
```

的时候,我们不仅应该设置 `color: red`,而且应该讲之前的 `fontSize` 去除,恢复为默认值。

总而言之,用一句话概括 `updateNodeProperties` 的过程:**先重置之前的 props,再设置新的 props**

代码如下(为了简化整个过程,我们忽略了第二点):

```js
function updateNodeProperties(prevProps, nextProps) {
let styleUpdates = {}

Object.keys(prevProps).forEach((propName) => {
if (propName === 'style') {
Object.keys(prevProps.style).forEach((styleName) => {
styleUpdates[styleName] = ''
})
} else {
DOM.removeProperty(this._domNode, propName)
}
})

Object.keys(nextProps).forEach((propName) => {
if (propName === 'style') {
Object.keys(nextProps.style).forEach((styleName) => {
styleUpdates[styleName] = nextProps.style[styleName]
})
} else {
DOM.setProperty(this._domNode, propName, nextProps[propName])
}
})

// update styles based on the `styleUpdates` object
updateStyles(this._domNode, styleUpdates)
}

function updateStyles(node, style) {
Object.keys(style).forEach((styleName) => {
node.style[styleName] = style[styleName]
})
}
```

## `createInitialDOMChildren`

在设置好最外层 DOM 节点的属性后,剩下的任务是将遍历 `props.children` 并 mount 每一个子节点,并且 append 到当前的 DOM 节点上。在上节我们提到,借助于 Reconciller 的**多态**,我们统一了 React 各类组件的接口,其中之一就是 `mountComponent` 这个方法。不管是什么类型的组件,调用这个方法都会返回对应的真正的 DOM 节点。这样一来,`createInitialDOMChildren` 就很好实现了。

不考虑到之后的 update,我们的第一想法或许是这样的:

```js
_createInitialDOMChildren(props) {
if (
typeof props.children === 'string' ||
typeof props.children === 'number'
) {
const textNode = document.createTextNode(props.children)
this._domNode.appendChild(textNode)
} else if (props.children) {
const children = Array.isArray(props.children) ? props.children : [props.children]
children.forEach((child, i) => {
// element => component
const childComponent = instantiateComponent(child)
childComponent._mountIndex = i
// component => DOM node
const childNode = Reconciler.mountComponent(child)

DOM.appendChildren(this._domNode, childrenNodes)
})
}
}
```

到此为止我们实现了 mounting 的操作。

让我们来想一下这样做的优劣。

优点是显而易见的,直观明了,没有多余的操作。但是缺点却非常致命,每次 mount 之后,我们并没有**保存对 mount 节点的信息**,这就使之后 Virtual DOM 的 Diff 实现变得无从下手。事实上,React 并不是简单地像上文这样 mount component,与此同时,还在这个过程中生成了一个 hash tree。

`DOMComponent` 继承了 `MultiChild`,关于 mounting 和 update 的大部分复杂的操作都在在这个类里面,例如在这个过程中调用的 `mountChildren`。从源码中看出,与上面我们写的 `_createInitialChildren` 细微的差别是,源码中并没有简单的使用 `forEach` 直接遍历,而是使用了一个函数,叫做 `traverseAllChildren`,利用这个方法,在每次 mounting 和 update 的过程中,得以以一种附加 callback 的方式遍历所有子节点,并返回上文我们说的 hash tree。如果你有兴趣,可以阅读:

[DOMComponent.js](../dilithium/src/DOMComponent.js)
[MultiChild.js](../dilithium/src/MultiChild.js)
[traverseAllChildren.js](../dilithium/src/traverAllChildren.js)

在下篇中我们会讲解由 React 是怎么实现 `setState`,以及其引发的一系列更新操作的。
6 changes: 4 additions & 2 deletions blog/mounting.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Day2 - Mounting

在做好一定的预备知识的学习后,本篇我们只研究一个问题:**React 是如何把 Component 中的 JSX 映射到页面上真正的 DOM 节点的**
在做好一定的预备知识的学习后,本篇我们只研究一个问题:

**React 是如何把 Component 中的 JSX 映射到页面上真正的 DOM 节点的**

## 面向测试编程

Expand Down Expand Up @@ -131,7 +133,7 @@ function createElement(type, config, children) {
}
```

> Note
> Note:
> 暂时不支持函数式组件

## Element -> Component
Expand Down
5 changes: 4 additions & 1 deletion demo/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ class SmallHeaderWithState extends Dilithium.Component {
render() {
return (
<div>
<h5>SmallHeader</h5>
<div style={{
fontSize: '36px',
color: 'red'
}}>SmallHeader</div>
{ this.state.number }
</div>
)
Expand Down

0 comments on commit cc370a3

Please sign in to comment.