Skip to content

Commit 3b7779d

Browse files
committed
feat: vdom diff algorithm
1 parent 9bc7a68 commit 3b7779d

File tree

10 files changed

+443
-2
lines changed

10 files changed

+443
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 1.0.0 (2020-03-05)
1+
# 1.0.0 (2020-03-20)
22

33

44
### Bug Fixes

docs/.vuepress/utils/alias.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @Author: Rainy
33
* @Date: 2020-01-31 11:42:43
44
* @LastEditors: Rainy
5-
* @LastEditTime: 2020-03-04 22:17:41
5+
* @LastEditTime: 2020-03-19 17:25:45
66
*/
77

88
const alias = {
@@ -25,6 +25,9 @@ const alias = {
2525
'debounce': '防抖',
2626
'throttle': '节流',
2727

28+
// vdom-diff
29+
'vdom-diff': '实现React/Vue DOM Diff算法',
30+
2831
// es6
2932
'es6': 'ECMAScript 6(ES6)',
3033
'promise': '手写Promise',

docs/zh/vdom-diff/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
## 原理
2+
3+
4+
5+
### 先序深度遍历
6+
7+
- 先中后序遍历 -> 遍历根节点的顺序
8+
9+
- 先序 -> 根, 左, 右
10+
11+
- 中序 -> 左, 根, 右
12+
13+
- 后序 -> 左, 右, 根
14+
15+
- 深度(DFS)遍历 -> 从根节点出发, 沿着左子树方向进行纵向遍历, 直到找到叶子节点为止。然后回溯到前一个节点, 进行右子树节点的遍历, 直到遍历完所有可达节点为止。
16+
17+
- 广度(BFS)遍历 -> 从根节点出发, 在横向遍历二叉树层段节点的基础上纵向遍历二叉树的层次。
18+
19+
![dom-diff-alogrithm.png](~@images/src/vdom-diff/images/dom-diff-alogrithm.png)
20+
21+
## Demo
22+
23+
[Link](https://rain120.github.io/vdom-diff-algorithm/)
24+
25+
<iframe name='demo' src='https://rain120.github.io/vdom-diff-algorithm/' width="100%" height="500px" frameborder="0" />
26+
27+
## 实现代码
28+
29+
[Link](https://github.com/Rain120/vdom-diff-algorithm/tree/master/src)
30+
31+
**index.d.ts**
32+
33+
<<< @/src/vdom-diff/src/index.d.ts
34+
35+
**utils.ts**
36+
37+
<<< @/src/vdom-diff/src/utils.ts
38+
39+
**render.js**
40+
41+
<<< @/src/vdom-diff/src/render.js
42+
43+
**diff.ts**
44+
45+
<<< @/src/vdom-diff/src/diff.ts
46+
47+
**patch.ts**
48+
49+
<<< @/src/vdom-diff/src/patch.ts
50+
51+
52+
## 参考
53+
54+
[React Diffing 算法](https://zh-hans.reactjs.org/docs/reconciliation.html#the-diffing-algorithm)
55+
56+
[React's diff algorithm - Christopher Chedeau](https://calendar.perfplanet.com/2013/diff/)
57+
58+
[React Dom Diff](https://sekaiamber.github.io/react-dom-diff/)
59+
60+
[virtual-dom](https://github.com/Matt-Esch/virtual-dom)
61+
62+
[How to write your own Virtual DOM](https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060)
63+
64+
[Under-the-hood-ReactJS](https://github.com/Bogdan-Lyashenko/Under-the-hood-ReactJS)
65+
66+
[babel-plugin-transform-react-jsx](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx/)

src/vdom-diff/README.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
## 原理
2+
3+
### 通过 JavaScript对象来描述真实DOM
4+
5+
**我们通过@babel/plugin-transform-react-jsx 这个babel插件来完成这一步**
6+
7+
#### Example
8+
9+
**React**
10+
11+
**In**
12+
13+
```jsx
14+
const profile = (
15+
<div>
16+
<img src="avatar.png" className="profile" />
17+
<h3>{[user.firstName, user.lastName].join(' ')}</h3>
18+
</div>
19+
);
20+
```
21+
22+
**Out**
23+
```jsx
24+
const profile = React.createElement("div", null,
25+
React.createElement("img", { src: "avatar.png", className: "profile" }),
26+
React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
27+
);
28+
```
29+
更多详情请移步 [@babel/plugin-transform-react-jsx](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx/)
30+
31+
或者你也可以通过 `ES6``class`关键字来来描述`DOM`, 例子如下:
32+
33+
```js
34+
class Element {
35+
type: string;
36+
props: Object<any>;
37+
children: Array<any>;
38+
39+
constructor(type: stirng, props: Object<any>, children: Array<any>) {
40+
this.type = type;
41+
this.props = props;
42+
this.children = children;
43+
}
44+
}
45+
```
46+
47+
### 渲染真实DOM
48+
49+
#### [createElement()](./src/patch.js)
50+
51+
The javascript virtual dom renders to the HTML real dom.
52+
53+
```js
54+
if (isString(node)) {
55+
return document.createTextNode(node + '');
56+
}
57+
const el = document.createElement(node.type);
58+
setProps(el, node.props || {});
59+
node.children &&
60+
node.children.map(createElement).forEach(el.appendChild.bind(el));
61+
62+
return el;
63+
```
64+
65+
### [Diff twice virtual dom](./src/diff.js)
66+
67+
```js
68+
function diff(oldNode, newNode) {
69+
if (!oldNode) {
70+
return { type: CREATE, newNode };
71+
}
72+
73+
if (!newNode) {
74+
return { type: REMOVE };
75+
}
76+
77+
if (changed(oldNode, newNode)) {
78+
return { type: REPLACE, newNode };
79+
}
80+
81+
if (oldNode.type !== newNode.type) {
82+
return {
83+
type: UPDATE,
84+
props: diffProps(oldNode.props, newNode.props),
85+
children: diffChildren(oldNode.children, newNode.children)
86+
};
87+
}
88+
}
89+
```
90+
91+
### [Patch twice virtual dom](./src/patch.js)
92+
93+
```js
94+
function patch(parent, patches, index = 0) {
95+
if (!patches) {
96+
return;
97+
}
98+
const el = parent.children[index];
99+
100+
switch (patches.type) {
101+
case CREATE: {
102+
const { newNode } = patches;
103+
const newEl = document.createElement(newNode);
104+
return parent.appendChild(newEl);
105+
}
106+
case REMOVE: {
107+
return parent.removeChild(el);
108+
}
109+
case REPLACE: {
110+
const { newNode } = patches;
111+
const newEl = createElement(newNode);
112+
return parent.replaceChild(newEl, el);
113+
}
114+
case UPDATE: {
115+
const { props, children } = patches;
116+
patchProps(el, props);
117+
children.forEach((child, idx) => {
118+
patch(el, child, idx);
119+
});
120+
}
121+
}
122+
}
123+
```
124+
125+
### 先序深度遍历
126+
127+
![dom-diff-alogrithm.png](./images/dom-diff-alogrithm.png)
128+
129+
## Demo
130+
131+
[Link](https://rain120.github.io/vdom-diff-algorithm/)
132+
133+
## 参考
134+
135+
[React Diffing 算法](https://zh-hans.reactjs.org/docs/reconciliation.html#the-diffing-algorithm)
136+
137+
[React's diff algorithm - Christopher Chedeau](https://calendar.perfplanet.com/2013/diff/)
138+
139+
[React Dom Diff](https://sekaiamber.github.io/react-dom-diff/)
140+
141+
[virtual-dom](https://github.com/Matt-Esch/virtual-dom)
142+
143+
[How to write your own Virtual DOM](https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060)
144+
145+
[Under-the-hood-ReactJS](https://github.com/Bogdan-Lyashenko/Under-the-hood-ReactJS)
146+
147+
[babel-plugin-transform-react-jsx](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx/)
308 KB
Loading

src/vdom-diff/src/diff.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {
2+
CREATE,
3+
REMOVE,
4+
REPLACE,
5+
UPDATE,
6+
SET_PROP,
7+
REMOVE_PROP
8+
} from './utils';
9+
10+
import { Node, AnyObject, AnyArray } from './index';
11+
12+
function changed(node1: Node | any, node2: Node | any): boolean {
13+
return (
14+
typeof node1 !== typeof node2 ||
15+
node1.type !== node2.type ||
16+
(typeof node1 !== 'string' && node1 !== node2)
17+
);
18+
}
19+
20+
function diffProps(oldProps: AnyObject, newProps: AnyObject): AnyArray {
21+
const patches: AnyArray = [];
22+
const props: AnyObject = Object.assign({}, oldProps, newProps);
23+
Object.keys(props).forEach(key => {
24+
const oldValue: any = oldProps[key];
25+
const newValue: any = newProps[key];
26+
27+
if (!newValue) {
28+
patches.push({
29+
type: REMOVE_PROP,
30+
name,
31+
value: oldValue
32+
});
33+
} else if (!oldValue || oldValue !== newValue) {
34+
patches.push({
35+
type: SET_PROP,
36+
name,
37+
value: newValue
38+
});
39+
}
40+
});
41+
return patches;
42+
}
43+
44+
function diffChildren(oldChildren: AnyArray, newChildren: AnyArray): AnyArray {
45+
const patches: AnyArray = [];
46+
const patchesLength: number = Math.max(newChildren.length, oldChildren.length);
47+
48+
for (let i = 0; i < patchesLength; i++) {
49+
patches[i] = diff(oldChildren, newChildren);
50+
}
51+
return patches;
52+
}
53+
54+
function diff(oldNode: Node | any, newNode: Node | any): AnyObject | undefined {
55+
if (!oldNode) {
56+
return { type: CREATE, newNode };
57+
}
58+
59+
if (!newNode) {
60+
return { type: REMOVE };
61+
}
62+
63+
if (changed(oldNode, newNode)) {
64+
return { type: REPLACE, newNode };
65+
}
66+
67+
if (oldNode.type !== newNode.type) {
68+
return {
69+
type: UPDATE,
70+
props: diffProps(oldNode.props, newNode.props),
71+
children: diffChildren(oldNode.children, newNode.children)
72+
};
73+
}
74+
}
75+
76+
export { changed, diffProps, diffChildren, diff };

src/vdom-diff/src/index.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export interface Node {
2+
type: string;
3+
props: AnyObject;
4+
children: Array<Node>;
5+
}
6+
7+
export type AnyObject = {
8+
[propName: string]: any;
9+
}
10+
11+
export type AnyArray = unknown[];

0 commit comments

Comments
 (0)