文章目录

Vue3 h函数手写渲染函数替代模板的场景

发布于 2026-04-29 00:21:20 · 浏览 5 次 · 评论 0 条

Vue3 h函数手写渲染函数替代模板的场景

Vue 3 的 h 函数(即 hyperscript)提供了一种直接通过 JavaScript 代码创建虚拟节点(VNode)的方式。在某些特定场景下,放弃模板转而使用 h 函数手写渲染函数,能显著提升代码的灵活性和控制力。


理解 h 函数的核心参数

在使用 h 函数前,必须明确其接收的三个核心参数。

$$ h(type, props, children) $$

其中 type 可以是 HTML 标签字符串、组件或异步组件;props 是传递的属性、事件、类等;children 是子节点,可以是字符串、数组或对象。掌握了这个公式,就掌握了渲染函数的基石。


场景一:动态标签的底层实现

当组件的根元素标签需要根据外部条件动态变化时,使用 h 函数比模板中的 <component :is="..."> 更加底层且高效。

  1. 定义 动态标签的 Props。
    script setup 中声明一个接收标签名的 prop。

  2. 编写 渲染逻辑。
    判断 传入的标签类型,直接将其作为 h 函数的第一个参数。

import { h } from 'vue'

const DynamicTag = (props, { slots }) => {
  // 直接将 props.tag 作为第一个参数传入 h 函数
  return h(props.tag, { class: 'dynamic-wrapper' }, slots.default ? slots.default() : '默认内容')
}
  1. 调用 该组件。
    在父组件中传入不同的标签名,组件会自动渲染为对应的 HTML 元素。

场景二:构建高阶组件(HOC)或逻辑包装器

若需创建一个不改变原有组件逻辑,但额外添加功能(如权限控制、加载状态、数据埋点)的包装器,h 函数是最佳选择。

  1. 创建 包装器组件。
    新建一个 WithPermission.vue 文件。

  2. 设计 权限拦截逻辑。
    setup 函数中,检查 当前用户的权限标识。

  3. 执行 条件渲染。
    如果权限通过,渲染 传入的插槽内容;否则,渲染 一个无权限提示组件或 null

import { h } from 'vue'

const WithPermission = (props, { slots }) => {
  const hasPermission = true // 假设这是权限校验逻辑

  if (!hasPermission) {
    // 权限不足时渲染空或提示
    return h('div', { style: 'color: red' }, '无权访问')
  }

  // 权限足够时,渲染默认插槽内容
  // 注意:slots.default() 返回的是 VNode 数组
  return h('div', { class: 'permission-wrapper' }, slots.default ? slots.default() : [])
}
  1. 使用 包装器。
    将需要受保护的组件包裹在 WithPermission 中。
<template>
  <WithPermission>
    <button>删除用户</button>
  </WithPermission>
</template>
<script setup>
import WithPermission from './WithPermission.vue'
</script>

场景三:处理极度复杂的循环或列表渲染

v-for 指令配合 v-if 变得极其复杂,或者列表结构需要根据数据深度动态变化时,模板的可读性会急剧下降。此时,使用 JavaScript 的 maph 函数组合会更加清晰。

  1. 准备 多层级数据源。
    假设有一个包含多级嵌套菜单的树形结构数据。

  2. 编写 递归渲染函数。
    定义 一个内部函数,接收当前层级的数据,并返回 h 生成的 VNode。

const renderTree = (nodes) => {
  if (!nodes || !nodes.length) return null

  return h(
    'ul',
    { class: 'tree-list' },
    nodes.map(node => {
      // 递归调用 renderTree 处理子节点
      const childrenVNodes = renderTree(node.children)

      return h(
        'li',
        { key: node.id },
        [
          h('span', { class: 'node-label' }, node.name),
          childrenVNodes
        ]
      )
    })
  )
}

const TreeView = (props) => {
  return renderTree(props.data)
}
  1. 传入 数据。
    在父组件中直接将树形数据传递给 TreeView 组件。

场景四:JSX 的替代方案与原生优势

虽然 Vue 3 支持 JSX,但 JSX 需要额外的编译配置。h 函数作为 Vue 的原生 API,不需要任何预处理器即可直接在 .js.ts 文件中运行。

  1. 处理 事件修饰符。
    在模板中我们习惯使用 .stop.prevent 等修饰符,但在 h 函数中,需手动处理事件对象。

  2. 编写 手动阻止默认行为代码。

import { h } from 'vue'

const MyForm = () => {
  return h('form', {
    onSubmit: (event) => {
      event.preventDefault() // 手动调用 preventDefault
      console.log('表单提交')
    }
  }, [
    h('button', { type: 'submit' }, '提交')
  ])
}
  1. 绑定 多个类名或样式。
    使用对象语法或数组语法直接作为 props 传入 h 函数,这与模板中的 :class:style 行为一致。
h('div', {
  class: [
    'base-class',
    {
      'active': isActive,
      'disabled': isDisabled
    }
  ],
  style: {
    color: 'red',
    fontSize: '16px'
  }
}, '内容文本')

场景五:函数式组件的极致简化

在 Vue 3 中,函数式组件被定义为纯函数。这类组件没有状态 (data),没有实例 (this),只接收 propscontext。对于纯展示型的 UI 组件(如按钮、标签、徽章),使用 h 函数编写性能开销最小。

  1. 声明 组件为一个常量。
    直接将箭头函数赋值给常量。

  2. 返回 虚拟 DOM。
    在函数体内直接 return h(...) 的结果。

// SmartButton.js
import { h } from 'vue'

export default (props) => {
  // 根据 type 属性决定颜色
  const colorMap = {
    primary: '#007bff',
    danger: '#dc3545',
    success: '#28a745'
  }

  return h(
    'button',
    {
      style: {
        backgroundColor: colorMap[props.type] || '#ccc',
        color: '#fff',
        padding: '10px 20px',
        border: 'none',
        borderRadius: '4px'
      },
      onClick: () => alert('点击了按钮')
    },
    props.label || '默认按钮'
  )
}
  1. 引入 并使用。
    在任何页面中引入该常量组件即可直接使用。

总结关键操作点

在使用 h 函数替代模板时,需牢记以下操作细节:

  • 扁平化数据结构props 对象中包含 classstyleid 以及 onXxx 事件处理器。
  • 子节点多样化:第三个参数 children 可以是字符串(文本节点)、数组(多个子节点)或 null(无子节点)。
  • 插槽处理:在 setup 的第二个参数或函数式组件的第二个参数中解构 slots,通过 slots.default() 调用来获取 VNode。

直接使用 h 函数虽然牺牲了模板的直观性,但在处理高度动态的 UI、底层库开发或极致性能优化的场景中,它是不可替代的利器。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文