|
| 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