19-实现 Fragment 节点和 Text 节点

377 阅读2分钟

Fragment

回顾创建slot的时候的代码

import { h } from '../h'

export function renderSlots(slots, name = 'default', props) {
  /** 返回一个vnode
   * 1. 其本质和 h 是一样的
   * 2. 通过name取到对应的slots
   */
  const slot = slots[name];
  if (slot) {
    if (typeof slot === "function") {
      // 是一个函数,需要调用函数,并把当前作用域插槽的数据传过去,把调用结果渲染处理
      return createdVNode('div', {}, slot(props));
    }
    // 不是函数,是h对象,或者h对象数组集合
    return createdVNode('div', {}, slot);
  } else {
    return console.warn("没有找到对应的插槽");
  }
}

问题:每次都是通过createdVNode去重新创建一个新的div节点,这样会导致一个问题。每个slot都会包一层div

image.png

很显然,这不优雅~ 可以通过Fragment节点来优化创建slot

修改slot创建节点

/*
 * @Author: Lin zefan
 * @Date: 2022-03-27 12:03:47
 * @LastEditTime: 2022-03-30 21:45:42
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\src\runtime-core\helpers\renderSlots.ts
 *
 */

import { createdVNode, Fragment } from "../vnode";

export function renderSlots(slots, name = "default", props = {}) {
  /** 返回一个vnode
   * 1. 其本质和 h 是一样的
   * 2. 通过name取到对应的slots
   */
  const slot = slots[name];
  if (slot) {
    if (typeof slot === "function") {
      // 是一个函数,需要调用函数,并把当前作用域插槽的数据传过去,把调用结果渲染处理
      return createdVNode(Fragment, {}, slot(props));
    }
    // 不是函数,是h对象,或者h对象数组集合
    return createdVNode(Fragment, {}, slot);
  } else {
    return console.warn("没有找到对应的插槽");
  }
}

新增Fragment节点类型

vnode.ts

export const Fragment = Symbol("Fragment");

render.ts

export function patch(vnode, container) {
  if (!vnode) return;
  const { type } = vnode;

  switch (type) {
    case Fragment:
      processFragment(vnode, container);
      break;

    default:
      if (isObject(type)) {
        // 是一个Component
        processComponent(vnode, container);
      } else if (typeof type === "string") {
        // 是一个element
        processElement(vnode, container);
      }
      break;
  }
}

element.ts

// 创建一个Fragment节点
export function processFragment(vnode: any, container: any) {
  mountChildren(vnode.children, container);
}

优化后

image.png

Text 节点

假如我们想实现纯文字的slot

 text: ()=> ("我是个text node"),

但是目前的逻辑是不支持textNode的,因为创建VNode的时候要么是H函数包裹的节点或者 Fragment 节点,所以要再加一个Text 节点

通过 createTextVNode 创建 TextVNode

vnode.ts

export const TextNode = Symbol("TextNode");
// 创建一个textNode节点
export function createTextVNode(text) {
  return createdVNode(TextNode, {}, text);
}

patch 时候新增TextNode判断

render.ts

export function patch(vnode, container) {
  if (!vnode) return;
  const { type } = vnode;

  switch (type) {
    case Fragment:
      processFragment(vnode, container);
      break;
    case TextNode:
      processTextNode(vnode, container);
      break;

    default:
      if (isObject(type)) {
        // 是一个Component
        processComponent(vnode, container);
      } else if (typeof type === "string") {
        // 是一个element
        processElement(vnode, container);
      }
      break;
  }
}

element.ts

// 创建一个TextNode节点
export function processTextNode(vnode: any, container: any) {
  const textNode = document.createTextNode(vnode.children);
  container.append(textNode);
}

实现

App.js

import { h, createTextVNode } from "../../lib/mini-vue.esm.js";
import Child from "./Child.js";

export default {
  name: "App",
  setup() {},

  render() {
    return h("div", {}, [
      h("div", {}, "你好"),
      h(
        Child,
        {},
        {
          header: [h("div", {}, "header")],
          default: ({ age }) => [
            h("p", {}, "我是通过 slot 渲染出来的第一个元素 "),
            h("p", {}, "我是通过 slot 渲染出来的第二个元素"),
            h("p", {}, `我可以接收到 age: ${age}`),
          ],
          main: h("div", {}, "main"),
          footer: ({ name }) =>
            h("p", {}, "我是通过footer插槽 ,名字是:" + name),
          text: createTextVNode("我是个text node"),
        }
      ),
    ]);
  },
};

image.png