持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情
本篇文章会为之前实现的vnode进行扩展,扩展一个新的类型的vnode -- Fragment,其对应于原生DOM中的Fragment结点
1. Fragment介绍
在上一小节中,我们实现了组件slots功能,但是有一个问题,只要是使用了插槽,就会将插槽内容包裹在div标签内再进行渲染
这个包裹的div实际上是没有什么必要存在的,那么有没有办法优化以下这个问题呢?
export function renderSlots(slots, name, props) {
const slot = slots[name];
if (slot) {
if (typeof slot === 'function') {
// 只需要渲染 slot 的内容,外层的 div 没必要渲染
return createVNode('div', {}, slot(props));
}
}
}
事实上是有的,那就是使用Fragment类型的vnode去渲染,Fragment是干什么的呢?
它能够做到只渲染**children**,不添加任何包裹标签的功能
2. 简单实现
首先我们可以简单实现一下,实现之后再重构,既然不希望包裹div标签,那么我们用一个特殊的vnode的type -- Fragment去替代
export function renderSlots(slots, name, props) {
const slot = slots[name];
if (slot) {
if (typeof slot === 'function') {
- return createVNode('div', {}, slot(props));
+ return createVNode('Fragment', {}, slot(props));
}
}
}
然后来到patch函数这里处理对type为Fragment这一特殊字符串的处理
export function patch(vnode, container) {
const { type, shapeFlag } = vnode;
switch (type) {
case 'Fragment':
processFragment(vnode, container);
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// 真实 DOM
processElement(vnode, container);
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
// 处理 component 类型
processComponent(vnode, container);
}
break;
}
}
当遇到Fragment类型的vnode的时候,就调用processFragment去进行处理
function processFragment(vnode: any, container: any) {
mountChildren(vnode.children, container);
}
由于我们就是希望直接将所有子元素渲染出来,所以只需要在这里面调用mountChildren即可
现在再看看渲染结果就会发现没有那层不必要的div了
3. 重构优化
1. 将Fragment字符串硬编码重构成Symbol
目前Fragment这个字符串在renderSlots和patch中都有用到,已经出现在两处地方了,因此可以考虑用Symbol重构,在vnode.ts中定义并导出Fragment这一Symbol
// src/runtime-core/vnode.ts
export const Fragment = Symbol('Fragment');
然后替换掉原来的字符串硬编码
- import { createVNode } from '../vnode';
+ import { createVNode, Fragment } from '../vnode';
export function renderSlots(slots, name, props) {
const slot = slots[name];
if (slot) {
if (typeof slot === 'function') {
- return createVNode('Fragment', {}, slot(props));
+ return createVNode(Fragment, {}, slot(props));
}
}
}
+ import { Fragment } from './vnode';
export function patch(vnode, container) {
const { type, shapeFlag } = vnode;
switch (type) {
- case 'Fragment':
+ case Fragment:
processFragment(vnode, container);
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// 真实 DOM
processElement(vnode, container);
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
// 处理 component 类型
processComponent(vnode, container);
}
break;
}
}