Vue 3 渲染函数实战:用 h() 与 JSX 写模板表达不出的动态 UI

test12026-10-075 次阅读

模板不是万能的

Vue 单文件组件用模板写结构,直观、可读,日常开发几乎都用它。但有些场景模板会显得笨拙:标签名、属性名、子节点数量在运行时才确定;要根据一份配置数组动态拼出完全不同的结构;要做高阶组件把传入的默认插槽包一层。这类"结构本身在变"的需求,用渲染函数比在模板里堆 v-if、v-for 清楚得多。

h() 是最基础的渲染函数

h 是 hyperscript 的缩写,三个参数分别是标签、属性、子节点。下面这个组件把一份词云配置渲染成不同字号、不同透明度的 span,标签类型、样式、事件全在 JS 里拼出来,模板很难一眼表达。

import { defineComponent, h, ref } from "vue";

export const TagCloud = defineComponent({
  props: {
    words: {
      type: Array as () => { text: string; weight: number }[],
      required: true,
    },
  },
  setup(props) {
    const pick = ref(0);
    return () =>
      h(
        "div",
        { class: "cloud" },
        props.words.map((w, i) =>
          h(
            "span",
            {
              class: "word",
              style: {
                fontSize: `${12 + w.weight}px`,
                opacity: pick.value === i ? 1 : 0.6,
              },
              onClick: () => (pick.value = i),
            },
            w.text
          )
        )
      );
  },
});

写复杂了就上 JSX

嵌套一多,纯 h() 调用会嵌套得很深,可读性下降。Vue 支持 JSX,配合 @vitejs/plugin-vue-jsx 就能在 .tsx 里直接写类 HTML 结构,同时保留全部 JS 表达能力。

import { defineComponent, ref } from "vue";

export const TagCloud = defineComponent({
  props: {
    words: {
      type: Array as () => { text: string; weight: number }[],
      required: true,
    },
  },
  setup(props) {
    const pick = ref(0);
    return () => (
      <div class="cloud">
        {props.words.map((w, i) => (
          <span
            class="word"
            style={{
              fontSize: `${12 + w.weight}px`,
              opacity: pick.value === i ? 1 : 0.6,
            }}
            onClick={() => (pick.value = i)}
          >
            {w.text}
          </span>
        ))}
      </div>
    );
  },
});

渲染函数和模板不是二选一

渲染函数的真正价值是处理"逻辑决定结构"的情况。比如一个通用的容器组件,接收 render 函数作为插槽,把默认内容用一层带边框的卡片包起来:

export const Card = defineComponent({
  setup(_, { slots }) {
    return () =>
      h("div", { class: "card" }, slots.default ? slots.default() : []);
  },
});

这里 slots.default() 返回的是 VNode 数组,组件不关心里面是按钮还是表格,只负责加外壳。这种"包裹型"组件用渲染函数写几乎零成本,用模板反而要绕。

几个注意点

  • 渲染函数里没有 v-model、v-for 这些语法糖,双向绑定要手写 value 加 onInput,列表用 Array.map 替代。
  • class 在渲染函数里既可以是字符串,也可以是对象或数组;style 必须是对象,且要用驼峰写法。
  • JSX 需要编译插件,工程里要确认 vue-jsx 插件已接入,否则 .tsx 不会被正确转换。
  • 能用模板解决的静态结构,优先模板;只有当结构本身依赖运行时逻辑时才切渲染函数,二者按职责分工。

小结

渲染函数和 JSX 是 Vue 表达能力的上限,适合标签、属性、子节点在运行期才确定的动态组件与高阶封装。把它当作模板的补充而不是替代,该用模板时仍用模板,遇到"结构随逻辑变"的场景再切过去,代码才会既灵活又可读。

T

test1

文章作者

模板不是万能的 Vue 单文件组件用模板写结构,直观、可读,日常开发几乎都用它。但有些场景模板会显得笨拙:标签名、属性...

分类
技术
发布时间
2026-10-07
字数
约 2439 字
阅读
5 次