Skip to content

Commit 2a64d54

Browse files
committed
docs(24): 实现 Fragment 和 TextNode
1 parent 5c143d4 commit 2a64d54

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed
+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# 实现 Fragment 节点和 Text 节点
2+
3+
## 1. Fragment 节点
4+
5+
### 1.1 例子
6+
7+
我们在上一篇中已经实现了 slots,但是我们再向 slots 添加内容的时候,发现如果添加的内容超过了 1 个,最终还是要通过一个 `div` 包裹这两个元素的,这是为什么呢?
8+
9+
```ts
10+
import { h } from '../h'
11+
12+
export function renderSlots(slots, name = 'default', props) {
13+
const slot = slots[name]
14+
if (slot) {
15+
// 我们在 renderSlots 的时候就使用了 div 作为包裹
16+
// 这是因为我们没办法将 array 直接渲染出来
17+
return h('div', {}, slot(props))
18+
}
19+
}
20+
```
21+
22+
下面我们改如何实现呢?
23+
24+
```ts
25+
// render.ts
26+
27+
// other code ...
28+
29+
export function patch(vnode, container) {
30+
// 我们在 patch 的时候对类型进行判断
31+
// 这个时候我们可以添加一个 Fragment 类型,来对 Fragment 进行判断
32+
const { shapeFlags } = vnode
33+
if (shapeFlags & ShapeFlags.ELEMENT) {
34+
processElement(vnode, container)
35+
} else if (shapeFlags & ShapeFlags.STATEFUL_COMPONENT) {
36+
processComponent(vnode, container)
37+
}
38+
}
39+
40+
// other code ...
41+
```
42+
43+
### 1.2 实现
44+
45+
首先,我们可以在 renderSlots 的时候从生成 div 到生成 Fragment
46+
47+
```ts
48+
export function renderSlots(slots, name = 'default', props) {
49+
// 此时 slots 就是 Object
50+
const slot = slots[name]
51+
if (slot) {
52+
// 从 div -> Fragment
53+
return h('Fragment', {}, slot(props))
54+
}
55+
}
56+
```
57+
58+
为了避免与用户的组件重名,我们可以生成一个 Symbol
59+
60+
```ts
61+
// vnode.ts
62+
export const Fragment = Symbol('Fragment')
63+
```
64+
65+
```ts
66+
import { Fragment } from '../vnode'
67+
68+
export function renderSlots(slots, name = 'default', props) {
69+
const slot = slots[name]
70+
if (slot) {
71+
// 从 'Fragement' -> Symbol
72+
return h(Fragment, {}, slot(props))
73+
}
74+
}
75+
```
76+
77+
这样我们在 patch 的时候就可以对类型进行判断了
78+
79+
```ts
80+
// render.ts
81+
82+
export function patch(vnode, container) {
83+
const { type, shapeFlags } = vnode
84+
switch (type) {
85+
// 对类型进行判断
86+
// 如果是 Fragement
87+
case Fragment:
88+
// 走 processFragment 的逻辑
89+
processFragment(vnode, container)
90+
break
91+
default:
92+
if (shapeFlags & ShapeFlags.ELEMENT) {
93+
processElement(vnode, container)
94+
} else if (shapeFlags & ShapeFlags.STATEFUL_COMPONENT) {
95+
processComponent(vnode, container)
96+
}
97+
break
98+
}
99+
}
100+
101+
102+
function processFragment(vnode, container) {
103+
// 因为 fragment 就是用来处理 children 的
104+
mountChildren(vnode, container)
105+
}
106+
```
107+
108+
现在我们 slots 多个子节点就不再需要使用 div 来包裹了
109+
110+
## 2. Text 节点
111+
112+
我们的 slots 目前也不支持直接渲染一个 TextContent 节点
113+
114+
### 2.1 例子
115+
116+
```ts
117+
const foo = h(
118+
Foo,
119+
{},
120+
{
121+
header: ({ count }) => h('div', {}, '123' + count),
122+
// 渲染一个节点是无法进行渲染的
123+
footer: () => 'hello TextNode',
124+
}
125+
)
126+
```
127+
128+
所以我们需要新增一个 API,用户创建纯 TextNode
129+
130+
```ts
131+
footer: () => createTextVNode('hello TextNode'),
132+
```
133+
134+
### 2.2 实现
135+
136+
我们在 VNode 中来实现这个 API
137+
138+
```ts
139+
// vnode.ts
140+
141+
export const TextNode = Symbol('TextNode')
142+
143+
export function createTextVNode(text) {
144+
return createVNode(TextNode, {}, text)
145+
}
146+
```
147+
148+
在 render 中也要修改为对应的逻辑
149+
150+
```ts
151+
export function patch(vnode, container) {
152+
const { type, shapeFlags } = vnode
153+
switch (type) {
154+
case Fragment:
155+
processFragment(vnode, container)
156+
break
157+
// 新增这个判断
158+
case TextNode:
159+
processTextNode(vnode, container)
160+
break
161+
default:
162+
if (shapeFlags & ShapeFlags.ELEMENT) {
163+
processElement(vnode, container)
164+
} else if (shapeFlags & ShapeFlags.STATEFUL_COMPONENT) {
165+
processComponent(vnode, container)
166+
}
167+
break
168+
}
169+
}
170+
```
171+
172+
```ts
173+
function processTextNode(vnode, container) {
174+
// TextNode 本身就是纯 text
175+
const element = (vnode.el = document.createTextNode(vnode.children))
176+
container.appendChild(element)
177+
}
178+
```
179+
180+
现在我们也已经支持 TextNode 了。

0 commit comments

Comments
 (0)