在 Vue.js 中使用自定义渲染函数创建文本节点

Create text node with custom render function in Vue.js

我正在使用 my-link 组件根据需要在各种项目周围包装锚标记。为此,使用了自定义 render 方法 - 然而 createElement 方法只能创建 HTML 节点,创建纯文本节点似乎是不可能的。

当前场景

my-link 组件的用法

<template v-for="item in items">
  <h4>
    <my-link :url="item.url">{{ item.text }}</my-link>
  </h4>
</template>

my-link 组件的实现为 Link.vue

<script>
export default {
  name: 'my-link',
  props: { url: String },
  render(createElement) {
    if (this.url) {
      return createElement(
          'a', {
            attrs: { href: this.url }
          }, this.$slots.default
      );
    }

    return createElement(
        'span',
        this.$slots.default
    );
  }
};
</script>

结果HTML

<h4>
  <a url="/some-link">This item is linked</a>
</h4>
<h4>
  <span>Plain text item</span>
</h4>

期望的场景

在这种特定情况下,span 标记是多余的,可以避免 - 但是,我不清楚 Vue.js 如何以及是否完全可以做到这一点。一般来说,我想知道如何使用自定义 render 方法创建纯文本节点。

<h4>
  <a url="/some-link">This item is linked</a>
</h4>
<h4>
  Plain text item
</h4>

旁注:

创建纯文本节点的 Vue exposes an internal method on it's prototype called _v。您可以 return 从 render 函数调用此方法以呈现纯文本字符串的结果:

render(h){
    return this._v("my string value");
}

以这种方式公开它,并带有下划线前缀,可能表明它是作为私有 API 方法使用的,因此请谨慎使用。

如果你使用功能组件,"this"也是不可用的。在这种情况下,你应该调用context._v(),例如:

functional: true,
render(h, context){
    return context._v("my string value")
}

这与从插槽中提取文本(如在您的评论中,使用有用的 getChildrenTextContent)相结合将产生所需的结果。

您需要一个组件的根元素。在您的情况下,也许您可​​以使用 'h4' 作为根元素,因为我看到您无论如何都将其包含在 v-for 循环中。一旦你这样做了,你就可以像这样创建一个文本节点。

return createElement(
    'h3',
    {},
    ['Plain text item']
);

根据Vue文档,createElement的第三个参数可以是字符串也可以是数组,如果是字符串会转为文本节点。

https://vuejs.org/v2/guide/render-function.html#createElement-Arguments

在 Vue 2.5.x 中,您可以使用它来呈现文本节点:

render(createElement) {
    return createElement(() => {
        return document.createTextNode('Text');
    });
}

实际上,您可以通过将 MyLink 组件的实现更改为功能组件来避免使用上面答案中提到的 _v 方法(并可能避免使用稍后可能重命名的内部 Vue 方法)。功能组件不需要根元素,因此它可以避免在非 link 元素周围放置一个跨度。

MyLink 可以定义如下:

const MyLink = {
  functional: true,
  name: 'my-link',
  props: { url: String },
  render(createElement, context) {
    let { url } = context.props
    let slots = context.slots()
    if (url) {
      return createElement(
          'a', {
            attrs: { href: url }
          }, slots.default
      );
    }
    else {
      return slots.default 
    }
  }
};

然后要使用它,您可以在不同的组件中执行类似的操作:

<div>
  <h4 v-for="item in items">
    <my-link :url="item.url">{{ item.text }}</my-link>
  </h4>
</div>

参见:https://codepen.io/hunterae/pen/MZrVEK?editors=1010

此外,作为旁注,您的原始代码片段似乎将文件命名为 Link.vue。由于您正在定义自己的渲染函数而不是使用 Vue 的模板系统,理论上您可以将文件重命名为 Link.js 并删除开始和结束脚本标签,并且只拥有一个完整的 JS 组件文件。但是,如果您的组件包含自定义系统(您的代码片段没有),则此方法将不起作用。希望这有帮助。

不需要使用任何私有方法,其实很简单。

仅呈现文本的 VNode 具有未定义的所有属性,除了文本。

所以你可以

return { text: 'my text' };

如果您使用的是 TypeScript,您可能需要断言它是一个 VNode。

万一有人遇到这个问题并且当前正在使用 Vue 3:API 已更改。您目前有 2 个选项:

  • 安装 @vue/compat 以像在 Vue 2 中一样访问 _v()
  • 否则,请选择 createTextVNode()
import { createTextVNode, VNode } from 'vue';

export const renderMoney = (data: number): VNode =>
  createTextVNode(data.toLocaleString('en'));

在此示例 (Typescript) 中,您可以传入一个数字并返回纯文本 VNode