跳到主要内容

h

2023年06月10日
柏拉文
越努力,越幸运

一、认识


h() 创建虚拟 DOM 节点 (vnode)。这个名字来源于许多虚拟 DOM 实现默认形成的约定。一个更准确的名称应该是 createVnode(),但当你需要多次使用渲染函数时,一个简短的名字会更省力。

二、语法


h(type: string | Component , props?: object | null ,children ?: Children | Slot | Slots);
  • type: 既可以是一个字符串 (用于原生元素) 也可以是一个 Vue 组件定义

  • props: 要传递的 prop

  • children: 子节点。当创建一个组件的 vnode 时,子节点必须以插槽函数进行传递。如果组件只有默认槽,可以使用单个插槽函数进行传递。否则,必须以插槽函数的对象形式来传递。

三、h element


3.1 两个参数

没有 props 时可以省略不写

const vnode = h('div', [h('div', '嘻嘻,哈哈'), h('div', '呵呵')])

render(vnode, document.querySelector('#app'))

3.2 三个参数

const vnode = h(
'div',
{
id: 'div-id',
class: 'div-class',
onClick: () => {
console.log('事件')
}
},
[h('div', '嘻嘻,哈哈'), h('div', '呵呵')]
)

console.log('vnode', vnode)
render(vnode, document.querySelector('#app'))

四、h component


4.1 对象

const Component = {
name: '',
setup() {},
render() {
return h('div', {}, 'Component VNode')
}
}

const vnode = h(Component)
console.log('vnode', vnode)

render(vnode, document.querySelector('#app'))

4.2 文件

在给组件创建 vnode 时,传递给 h() 函数的第一个参数应当是组件的定义。这意味着使用渲染函数时不再需要注册组件了 —— 可以直接使用导入的组件:

import Foo from "./Foo.vue";

const vnode = h(Foo);

render(vnode, document.querySelector('#app'))

不管是什么类型的文件,只要从中导入的是有效的 Vue 组件,h 就能正常运作。

五、h slot


const ChildComponent = {
name: 'ChildComponent',
setup(props, setupContext) {
const a = reactive({
b: 1,
c: 2
})
const { slots } = setupContext
return () => {
return h('div', null, [
h('div', null, 'Child Component'),
// 默认插槽 <div> <slot></slot> </div>
h('div', null, slots.default()),
// 具名插槽 <div> <slot name="header" ></slot> </div>
h('div', null, slots.header()),
// 作用域插槽 <div> <slot name="header" :a="a"></slot> </div>
h(
'div',
null,
slots.body({
a: a
})
)
])
}
}
}

const AppComponent = {
name: 'AppComponent',
setup() {},
render() {
return h('div', {}, [
h('div', null, 'App Component'),
h(ChildComponent, null, {
default() {
return h('div', null, 'default 插槽渲染内容')
},
header() {
return h('div', null, 'header 具名插槽渲染内容')
},
body(slotProps) {
const { a } = slotProps
return h('div', null, [
h('div', null, 'body 作用域插槽渲染内容'),
h(
'div',
null,
`body 作用域插槽中 子组件传递给父组件 的数据 ${a.b}`
)
])
}
})
])
}
}

const vnode = h(AppComponent)
console.log('vnode', vnode)

render(vnode, document.querySelector('#app'))

六、h KeepAlive


<KeepAlive> 内置组件在渲染函数中必须导入才能使用

const ChildComponent = {
name: 'ChildComponent',
setup() {},
render() {
return h('div', null, [h('div', null, 'ChildComponent')])
}
}

const AppComponent = {
name: 'AppComponent',
setup() {},
render() {
return h('div', null, [
h('div', null, 'AppComponent'),
h(KeepAlive, { mode: 'out-in' }, [h(ChildComponent)])
])
}
}

const vnode = h(AppComponent)

render(vnode, document.querySelector('#app'))

七、h Text


const vnode = h(Text, 'Text VNode')
console.log('vnode', vnode)

render(vnode, document.querySelector('#app'))

八、h Comment


const vnode = h(Comment, 'Comment VNoe')
console.log('vnode', vnode)

render(vnode, document.querySelector('#app'))

九、h Fragment


const vnode = h(Fragment, null, [h('div', null, 'Fragment VNode')])
console.log('vnode', vnode)

render(vnode, document.querySelector('#app'))

十、h v-model


v-model 指令扩展为 modelValueonUpdate:modelValue 在模板编译过程中,我们必须自己提供这些 props:

const AppComponent = {
name: 'AppComponent',
setup(props, setupContext) {
const a = ref('')
const { emit } = setupContext

return () =>
h('div', null, [
h('div', null, 'AppComponent'),
h('input', {
modelValue: a.value,
'onUpdate:modelValue': value => emit('update:modelValue', value)
})
])
}
}

const vnode = h(AppComponent)

render(vnode, document.querySelector('#app'))

十一、h v-if


const flag = false
const vnode = h('div', null, flag ? h('div', '嘻嘻') : h('div', '哈哈'))

console.log('vnode', vnode)
render(vnode, document.querySelector('#app'))

十二、h v-for


const list = [1, 2, 3]
const vnode = h(
'div',
null,
list.map(item => h('div', null, item))
)

console.log('vnode', vnode)
render(vnode, document.querySelector('#app'))

十三、h v-on


13.1 驼峰拼接

对于 .passive.capture.once 事件修饰符,可以使用驼峰写法将他们拼接在事件名后面:

const vnode = h(
'div',
{
onClickOnce: () => {
console.log('哈哈')
}
},
'嘻嘻'
)

render(vnode, document.querySelector('#app'))

13.2 withModifiers

对于事件和按键修饰符,可以使用 withModifiers 函数:

const vnode = h(
'div',
{
onClick: withModifiers(() => {
console.log('哈哈')
}, ['self'])
},
'嘻嘻'
)

render(vnode, document.querySelector('#app'))

十四、h 动态组件


14.1 直接导入

import A from './a.vue'
import B from './b.vue'
import { h, render, withModifiers } from 'vue'

const flag = true
const vnode = h('div', null, flag ? A : B)

render(vnode, document.querySelector('#app'))

14.2 动态导入

如果一个组件是用名字注册的,不能直接导入 (例如,由一个库全局注册),可以使用 resolveComponent() 来解决这个问题。

const AppComponent = {
name: 'AppComponent',
setup() {
const AComponent = resolveComponent('A')

return () => {
return h('div', null, AComponent)
}
}
}
const vnode = h(AppComponent)

render(vnode, document.querySelector('#app'))