Skip to content

Commit d28e21c

Browse files
committed
feat(*) add demo03
1 parent d9af6bb commit d28e21c

File tree

7 files changed

+223
-8
lines changed

7 files changed

+223
-8
lines changed

01.从零构建一个简单的 react.md

+144-6
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,16 @@ ReactDOM.render(element, container);
105105
如果用 `TypeScript` 来表示这个对象,可以写为:
106106

107107
```ts
108-
export interface IFiber {
108+
export interface IPropsFiber {
109109
type: string;
110110
props: {
111-
children?: IFiber[];
111+
children?: IPropsFiber[];
112112
[props: string]: any
113113
};
114114
}
115115
```
116116

117-
注意到这里以 `fiber` 为类型名,这里先不展开讲 `fiber`, 我们后续会更详细地介绍它
117+
注意到我们的类型定义里有 `fiber` 这个单词,这一个重要的概念,但是这里先不展开讲,我们后续会更详细介绍
118118

119119
言归正传,`createElement` 要做的事情很简单,就是根据入参转换出一个树结构出来,例如:
120120

@@ -186,7 +186,7 @@ const element = {
186186

187187
```ts
188188
createElement(type, props, ...children){
189-
// TODO
189+
// ...
190190
}
191191
```
192192

@@ -220,11 +220,149 @@ function createTextElement(text: string) {
220220
}
221221
```
222222

223-
运行 `yarn demo01` 可以查看效果。
223+
运行 `yarn demo02` 可以查看效果:
224+
225+
![](./images/02.png)
226+
227+
可看到一个树状的数据结构。
228+
229+
细心的同学会在 demo02 中看到 `@jsx` 的注释,如下:
230+
231+
```jsx
232+
/** @jsx createElement */
233+
const element = (<div title='foo'>
234+
<span id='a'>hello</span>
235+
<span id='b'>world</span>
236+
</div>);
237+
```
238+
239+
解释一下,加上这行注释 parcel 就会用 babel 相应的[插件](https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx)来解析 JSX 语法。
240+
241+
总结一下:**在第二步中,我们借助了 babel 的能力解析了 JSX 语法,然后通过我们自定义的 `createElement` 实现了树状结构。**
224242

225243
### 第三步:实现 render 方法
226244

227-
TODO
245+
实现 render 方法其实非常简单:
246+
247+
```ts
248+
function render(element, container) {
249+
const dom = document.createElement(element.type)
250+
container.appendChild(dom)
251+
}
252+
```
253+
254+
是的,就是这个可用的 `render` 方法。
255+
256+
但是,我们需要考虑递归的问题,因为 render 的第2个参数是一个树状的结构。
257+
258+
于是我们优化如下:
259+
260+
```diff
261+
function render(element, container) {
262+
const dom = document.createElement(element.type)
263+
+ element.props.children.forEach(child => render(child, dom))
264+
container.appendChild(dom)
265+
}
266+
```
267+
268+
考虑到 element 可能是原始数据类型,我们还需要继续优化:
269+
270+
```diff
271+
function render(element, container) {
272+
- const dom = document.createElement(element.type)
273+
+ const dom =
274+
+ element.type === 'TEXT_ELEMENT'
275+
+ ? document.createTextNode('')
276+
+ : document.createElement(element.type)
277+
element.props.children.forEach(child => render(child, dom))
278+
container.appendChild(dom)
279+
}
280+
```
281+
282+
另外,我们还要将树中属性写到 Dom 上:
283+
284+
```diff
285+
function render(element, container) {
286+
const dom =
287+
element.type === 'TEXT_ELEMENT'
288+
? document.createTextNode('')
289+
: document.createElement(element.type)
290+
291+
+ const isProperty = key => key !== "children"
292+
+ Object.keys(element.props)
293+
+ .filter(isProperty)
294+
+ .forEach(name => {
295+
+ dom[name] = element.props[name]
296+
+ })
297+
298+
element.props.children.forEach(child => render(child, dom))
299+
container.appendChild(dom)
300+
}
301+
```
302+
303+
注意到这里过滤了 `children` 属性,因为在 react 中 `children` 表示子元素。
304+
305+
先阶段完整的 `react` 代码如下:
306+
307+
```ts
308+
function createElement(type: string, props, ...children) {
309+
return {
310+
type,
311+
props: {
312+
...props,
313+
children: children.map(child =>
314+
typeof child === 'object'
315+
? child
316+
: createTextElement(child)
317+
)
318+
}
319+
};
320+
}
321+
322+
function createTextElement(text: string) {
323+
return {
324+
type: 'TEXT_ELEMENT',
325+
props: {
326+
nodeValue: text,
327+
children: []
328+
}
329+
};
330+
}
331+
332+
333+
function render(element, container) {
334+
const dom =
335+
element.type === 'TEXT_ELEMENT'
336+
? document.createTextNode('')
337+
: document.createElement(element.type);
338+
339+
const isProperty = key => key !== 'children';
340+
Object.keys(element.props)
341+
.filter(isProperty)
342+
.forEach(name => {
343+
dom[name] = element.props[name];
344+
});
345+
346+
element.props.children.forEach(child => render(child, dom));
347+
container.appendChild(dom);
348+
}
349+
350+
/** 以下是业务代码 **/
351+
352+
/** @jsx createElement */
353+
const element = (<div title='foo'>
354+
<span id='a'>hello</span>
355+
<span id='b'>world</span>
356+
</div>);
357+
358+
render(element, document.getElementById('root'));
359+
```
360+
361+
我们可以运行 `yarn demo03` 查看它的运行效果,和 react 是一样的效果。
362+
363+
### 第四步
364+
365+
<!-- TODO -->
228366
229367
### 相关链接
230368

article01/demo02/index.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,4 @@ document.getElementById('root').innerText = JSON.stringify(element, null, 2);
3232

3333
console.log(element);
3434

35-
3635
export { };

article01/demo03/index.html

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Document</title>
8+
</head>
9+
10+
<body>
11+
<pre id='root'></pre>
12+
</body>
13+
<script src="./index.tsx"></script>
14+
15+
</html>

article01/demo03/index.tsx

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
function createElement(type: string, props, ...children) {
2+
return {
3+
type,
4+
props: {
5+
...props,
6+
children: children.map(child =>
7+
typeof child === 'object'
8+
? child
9+
: createTextElement(child)
10+
)
11+
}
12+
};
13+
}
14+
15+
function createTextElement(text: string) {
16+
return {
17+
type: 'TEXT_ELEMENT',
18+
props: {
19+
nodeValue: text,
20+
children: []
21+
}
22+
};
23+
}
24+
25+
/** @jsx createElement */
26+
const element = (<div title='foo'>
27+
<span id='a'>hello</span>
28+
<span id='b'>world</span>
29+
</div>);
30+
31+
function render(element, container) {
32+
const dom =
33+
element.type === 'TEXT_ELEMENT'
34+
? document.createTextNode('')
35+
: document.createElement(element.type);
36+
37+
const isProperty = key => key !== 'children';
38+
Object.keys(element.props)
39+
.filter(isProperty)
40+
.forEach(name => {
41+
dom[name] = element.props[name];
42+
});
43+
44+
element.props.children.forEach(child => render(child, dom));
45+
container.appendChild(dom);
46+
}
47+
48+
render(element, document.getElementById('root'));
49+
50+
export { };

article01/demo03/package.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "simple-react",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"dependencies": {
7+
"react": "^17.0.1"
8+
},
9+
"devDependencies": {
10+
"parcel": "^1.12.4"
11+
}
12+
}

images/02.png

92.1 KB
Loading

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"start": "parcel src/index.html",
88
"build": "parcel build src/index.html",
99
"demo01": "parcel article01/demo01/index.html",
10-
"demo02": "parcel article01/demo02/index.html "
10+
"demo02": "parcel article01/demo02/index.html",
11+
"demo03": "parcel article01/demo03/index.html"
1112
},
1213
"dependencies": {
1314
"react": "^17.0.1",

0 commit comments

Comments
 (0)