实现mini-vue -- runtime-core模块(十)实现Fragment类型vnode

210 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

本篇文章会为之前实现的vnode进行扩展,扩展一个新的类型的vnode -- Fragment,其对应于原生DOM中的Fragment结点

1. Fragment介绍

在上一小节中,我们实现了组件slots功能,但是有一个问题,只要是使用了插槽,就会将插槽内容包裹在div标签内再进行渲染 image.png

这个包裹的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标签,那么我们用一个特殊的vnodetype -- 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函数这里处理对typeFragment这一特殊字符串的处理

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即可 现在再看看渲染结果就会发现没有那层不必要的divimage.png


3. 重构优化

1. 将Fragment字符串硬编码重构成Symbol

目前Fragment这个字符串在renderSlotspatch中都有用到,已经出现在两处地方了,因此可以考虑用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;
  }
}