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

862 阅读2分钟

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

上一篇文章我们实现了Fragment类型的vnode,这章来实现另一种类型的vnode -- Text类型

Text类型的vnode会被直接渲染成文本,不会有任何标签包裹,像之前如果我们需要渲染文本的话,是需要一个父标签包裹的,比如p标签、span标签等,而Text类型的vnode就能够打破这个限制

1. 分析要做的事情

有了前面Fragment的经验,现在实现Text就简单很多了,可以完全依葫芦画瓢,大概就是以下几步:

  1. 创建一个新的vnodetype -- Text,用Symbol实现
  2. patch的时候添加对typeText的特殊处理
  3. 调用原生jscreateTextNode去创建文本结点
  4. 将文本结点作为vnode.el添加到container

2. 使用场景

事实上就和处理Element类型是类似的,只是创建的不是普通结点,而是TextNode 无论是slots中要渲染的内容还是直接渲染的内容,都需要是vnode才行,因此我们不能像下面这样尝试直接渲染纯文本

import { h } from '../../lib/plasticine-mini-vue.esm.js';
import { Foo } from './Foo.js';

export const App = {
  name: 'App',
  setup() {
    return {};
  },
  render() {
    const foo = h(
      Foo,
      {},
      {
        // 不能直接渲染纯文本 因为 string 不是 vnode
        header: ({ age }) => [h('p', {}, 'header' + age), 'Text Node Content'],
        footer: () => h('p', {}, 'footer'),
      }
    );

    // const foo = h(Foo, {}, h('p', {}, 'default slot'));

    return h('div', {}, [foo]);
  },
};

3. createTextVNode代替h生成文本vnode

我们可以实现一个createTextVNode函数,创建一个typeTextvnode

import { h } from '../../lib/plasticine-mini-vue.esm.js';
import { Foo } from './Foo.js';

export const App = {
  name: 'App',
  setup() {
    return {};
  },
  render() {
    const foo = h(
      Foo,
      {},
      {
-       header: ({ age }) => [h('p', {}, 'header' + age), 'Text Node Content'],
+       header: ({ age }) => [
+         h('p', {}, 'header' + age),
+         createTextVNode('Text Node Content'),
+       ],
        footer: () => h('p', {}, 'footer'),
      }
    );

    // const foo = h(Foo, {}, h('p', {}, 'default slot'));

    return h('div', {}, [foo]);
  },
};

vnode.ts中实现createTextVNode以及定义一个Text类型

export const Text = Symbol('Text');
export function createTextVNode(text: string) {
  return createVNode(Text, {}, text);
}

4. patch中增加对Text类型的处理

再在patch中实现对Text类型vnode的处理

export function patch(vnode, container) {
  const { type, shapeFlag } = vnode;

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

    default:
      if (shapeFlag & ShapeFlags.ELEMENT) {
        // 真实 DOM
        processElement(vnode, container);
      } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
        // 处理 component 类型
        processComponent(vnode, container);
      }
      break;
  }
}

function processText(vnode: any, container: any) {
  const { children } = vnode;
  const textNode = (vnode.el = document.createTextNode(children));
  container.append(textNode);
}

和处理Element类型的vnode是很类似的,只不过Element类型的vnode需要先创建一个父容器,然后根据children是文本还是数组去执行不同的渲染逻辑 而Text则简单很多,不需要任何父容器,直接创建TextNode即可

5. runtime-core出口导出createTextVNode

最后别忘了在runtime-core的出口index.ts中将createTextVNode函数导出,从而能够被rollup打包到构建结果中

export { createApp } from './createApp';
export { h } from './h';
export { renderSlots } from './helpers/renderSlots';
export { createTextVNode } from './vnode';

image.png