vue3中h函数的使用

h函数是用于创建一个 vnodes ,它既可以用于创建原生元素,也可以创建组件,其渲染后的效果等同于使用模版语言来进行创建。

h函数的传参如下:

javascript 复制代码
// 完整参数签名
function h(
  type: string | Component,
  props?: object | null,
  children?: Children | Slot | Slots
): VNode

// 省略 props
function h(type: string | Component, children?: Children | Slot): VNode

第一个参数既可以是一个字符串 (用于原生元素) 也可以是一个 Vue 组件定义。第二个参数是要传递的 prop,第三个参数是子节点。

1、创建原生元素:

javascript 复制代码
<script setup>
import { ref, h } from 'vue'

const message = ref('在div里面渲染的值')
const comp = h(
  'div',
  {
    style: {
      color: 'red'
    },
    onclick: ()=> {
      console.log('点击了原生元素div');
    }
  },
  message.value
)
</script>

<template>
  <component :is="comp" />
</template>

这里给 div 传的 props 里有样式 style 和 事件 click ,在页面上的显示和点击了元素后效果和在模版语言中定义是一样的:

需要注意的是,我们这里的 comp 是一个 vnodes ,而 setup 函数并不是响应式的环境,所以当我们在 setup 函数中调用 h 函数来获取 vnodes 时,**并没有绑定 message !**只有在 render 函数 中执行才会绑定,所以我们可以定义一个h函数来返回给 comp,在渲染时,让component来帮我们调用。可以通过在 2s 后改变 message 的值来对比两种情况下页面渲染的变化:

javascript 复制代码
const message = ref('在div里面渲染的值')
// 这样不会更新 message 的值
// const comp = h(
//   'div',
//   {
//     style: {
//       color: 'red'
//     },
//     onclick: ()=> {
//       console.log('点击了原生元素div');
//     }
//   },
//   message.value
// )

// 这样可以更新 message 的值
const comp = () => h(
  'div',
  {
    style: {
      color: 'red'
    },
    onclick: ()=> {
      console.log('点击了原生元素div');
    }
  },
  message.value
)

setTimeout(()=> {
  message.value = '2s后更新了在div里面渲染的值'
}, 2000)

2、创建组件

这里使用的是一个批量注册的方式导入 HelloWorld 组件,Comp 是一个全局组件

全局组件的定义如下:

javascript 复制代码
// component.js

import { h } from 'vue'

const modules = import.meta.glob('../components/*.vue')

const components = {}
for(const path in modules) {
    const module = await modules[path]()
    const componentName = path.replace(/.*\/(.*)\.vue/, '$1')
    components[componentName] = module.default
}
const component = (props,{slots}) => {
    let name  =  props?.component
    return h(
        components[name], 
        { 
            msg: '通过props传的msg',
            onFoo: (value)=> {
                console.log(value);
            },
        },
        slots
    )
}

export default component

页面上导入是这样的:

容易混淆的地方是,Comp 是一个全局组件,通过传参 HelloWorld 渲染的才是 HelloWorld 组件,相当于外面套了一层,这里的 slots 其实是 Comp 组件的 slots , slots 传进了 HelloWorld 组件里,使用 HelloWolrd 组件里预留的插槽渲染的

javascript 复制代码
<Comp component="HelloWorld">
    我是 Comp 组件的默认插槽
    <template #header>
      <div>
        我是 Comp 组件的 header 插槽
      </div>
    </template>
  </Comp>

HelloWorld 组件中定义的:

javascript 复制代码
<template>
  <div>
    <div>{{msg}}</div>
    <div style="color: red">{{valueInProps}}</div>
    <slot></slot>
    <slot name="header" valueInSlot="我是 header 插槽里面的值">
      <div>
        我是 header 插槽里面的默认值,外部没有定义的话就是显示这个
      </div>
    </slot>
  </div>
</template>

那么 slots 里面到底是什么呢,我们直接打印一下看看:

我们可以看到 slots 其实是一个对象,键是插槽的名字,值其实就是一个 渲染函数 h(),

也可以这样写:

javascript 复制代码
const component = (props,{slots}) => {
    let name  =  props?.component
    console.log(slots);
    return h(
        components[name], 
        { 
            msg: '通过props传的msg',
            onFoo: (value)=> {
                console.log(value);
            },
        },
        slots.default()
    )
}

使用作用域插槽的话:

javascript 复制代码
const component = (props,{slots}) => {
    let name  =  props?.component
    return h(
        components[name], 
        { 
            msg: '通过props传的msg',
            onFoo: (value)=> {
                console.log(value);
            },
        },
        slots.default('我是作用域插槽传的值')
    )
}

<Comp component="HelloWorld">
    <template #default="scope">
      <div>
        {{scope}}
      </div>
    </template>
  </Comp>

如果我们不想帮 Comp 组件渲染的话,也可以自己来写:

javascript 复制代码
const component = (props,{slots}) => {
    let name  =  props?.component
    console.log(slots);
    return h(
        components[name], 
        { 
            msg: '通过props传的msg',
            onFoo: (value)=> {
                console.log(value);
            },
        },
        {
            default: ()=> h('div', '我是 HelloWorld 组件的默认插槽里面的值'),
            header: ()=> h('div', '我是 HelloWorld 组件的 header 插槽里面的值'),
        }
    )
}

如果我们想渲染预留插槽里面的值(即作用域插槽),可以这样传:

javascript 复制代码
<slot name="header" valueInSlot="我是 header 插槽里面的值">
      <div>
        我是 header 插槽里面的默认值,外部没有定义的话就是显示这个
      </div>
</slot>
javascript 复制代码
const component = (props,{slots}) => {
    let name  =  props?.component
    console.log(slots);
    return h(
        components[name], 
        { 
            msg: '通过props传的msg',
            onFoo: (value)=> {
                console.log(value);
            },
        },
        {
            default: ()=> h('div', '我是 HelloWorld 组件的默认插槽里面的值'),
            header: ({valueInSlot})=> h('div', '我是 HelloWorld 组件的 header 插槽里面的值,后面是预留插槽的值传递:'+valueInSlot),
        }
    )
}

页面上的显示效果:

还有两个比较好理解的点,这里也补充一下:

在组件中传值,我们知道是用 props 来进行传递,所以在子组件中也是用 defineProps 来 接收值,而子组件想要传值给父组件的话,注意如果是传 foo 函数,则要用 onFoo 接受,例子如下:

相关推荐
cs_dn_Jie3 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
Yaml46 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
计算机-秋大田15 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue
Yaml41 天前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
清灵xmf1 天前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
琴~~2 天前
前端根据后端返回的文本流逐个展示文本内容
前端·javascript·vue
程序员徐师兄3 天前
基于 JavaWeb 的宠物商城系统(附源码,文档)
java·vue·springboot·宠物·宠物商城
shareloke3 天前
让Erupt框架支持.vue文件做自定义页面模版
vue
你白勺男孩TT3 天前
Vue项目中点击按钮后浏览器屏幕变黑,再次点击恢复的解决方法
vue.js·vue·springboot
虞泽4 天前
鸢尾博客项目开源
java·spring boot·vue·vue3·博客