目录
前言
h() 函数是什么
h() 函数参数
h() 函数基本使用
h() 函数创建 vnodes
h() 函数 API
前言
vue 在绝大多数情况下都推荐使用模板来编写 html 结构,但是对于一些复杂场景下需要完全的 JS 编程能力,这个时候我们就可以使用渲染函数 ,它比模板更接近编译器
vue 在生成真实的 DOM 之前,会将我们的节点转换成 VNode,而 VNode 组合在一起形成一颗树结构,就是虚拟 DOM(VDOM)
我们之前编写的 template 中的 HTML 最终也是使用渲染函数生成对应的 VNode
h() 函数是什么
Vue 提供了一个 h()
函数用于创建 vnodes:
import { h } from 'vue'
const vnode = h(
'div', // type
{ id: 'foo', class: 'bar' }, // props
[
/* children */
]
)
h()
是 hyperscript 的简称——意思是“能生成 HTML (超文本标记语言) 的 JavaScript”。这个名字来源于许多虚拟 DOM 实现默认形成的约定。一个更准确的名称应该是 createVnode()
,但当你需要多次使用渲染函数时,一个简短的名字会更省力。
h()
函数参数
type
:类型参数,必填。一个html
标签名,一个组件或者一个异步组件,或函数式组件props
:props参数,非必填。一个对象,内容包括了即将创建的节点的属性,例如id
、class
、style
等,节点的事件监听也是通过 props 参数进行传递,并且以on
开头,以onXxx
的格式进行书写,如onInput
、onClick
等。不写的话最好用null
占位children
:子节点,非必填。内容可以是文本、虚拟 DOM 节点和插槽等等。
官方完整类型参数如下:
// 完整参数签名
function h(
type: string | Component,
props?: object | null,
children?: Children | Slot | Slots
): VNode
// 省略 props
function h(type: string | Component, children?: Children | Slot): VNode
type Children = string | number | boolean | VNode | null | Children[]
type Slot = () => Children
type Slots = { [name: string]: Slot }
h() 函数基本使用
h()
函数可以在两个地方使用:
render
函数中render() { return h( "div", { class: "app", }, [ // 这里this是可以取到setup中的返回值的 h("h2", null, `当前计数: ${this.counter}`), h("button", {onclick:() => this.counter++}, "+1"), h("button", {onclick:() => this.counter--}, "-1"), ] ); },
setup
函数中setup() { const counter = ref(0); return () => h( "div", { class: "app", }, [ // 这里this是可以取到setup中的返回值的 h("h2", null, `当前计数: ${counter.value}`), h("button", { onclick: () => counter.value++ }, "+1"), h("button", { onclick: () => counter.value-- }, "-1"), ] ); },
h() 函数创建 vnodes
- 创建原生元素
// 除了类型必填以外,其他的参数都是可选的 h('div') h('div', { id: 'foo' }) // attribute 和 property 都能在 prop 中书写 // Vue 会自动将它们分配到正确的位置 h('div', { class: 'bar', innerHTML: 'hello' }) // 像 `.prop` 和 `.attr` 这样的的属性修饰符 // 可以分别通过 `.` 和 `^` 前缀来添加 h('div', { '.name': 'some-name', '^width': '100' }) // 类与样式可以像在模板中一样 // 用数组或对象的形式书写 h('div', { class: [foo, { bar }], style: { color: 'red' } }) // 事件监听器应以 onXxx 的形式书写 h('div', { onClick: () => {} }) // children 可以是一个字符串 h('div', { id: 'foo' }, 'hello') // 没有 props 时可以省略不写 h('div', 'hello') h('div', [h('span', 'hello')]) // children 数组可以同时包含 vnodes 与字符串 h('div', ['hello', h('span', 'hello')])
- 创建组件
import Foo from './Foo.vue' // 传递 prop h(Foo, { // 等价于 some-prop="hello" someProp: 'hello', // 等价于 @update="() => {}" onUpdate: () => {} }) // 传递单个默认插槽 h(Foo, () => 'default slot') // 传递具名插槽 // 注意,需要使用 `null` 来避免 // 插槽对象被当作是 prop h(MyComponent, null, { default: () => 'default slot', foo: () => h('div', 'foo'), bar: () => [h('span', 'one'), h('span', 'two')] })
MyComponent 组件
render() {
return h("div", null, [
h("h2", null, "hello world"),
[
this.$slots.default ? this.$slots.default() : h("h2", null, "我是默认插槽"),
this.$slots.foo()
this.$slots.bar()
]
]);
}
h() 函数 API
{
// 和`v-bind:class`一样的 API
'class': {
foo: true,
bar: false
},
// 和`v-bind:style`一样的 API
style: {
color: 'red',
fontSize: '14px'
},
// 正常的 HTML 特性
attrs: {
id: 'foo'
},
// 组件 props
props: {
myProp: 'bar'
},
// DOM 属性
domProps: {
innerHTML: 'baz'
},
// 事件监听器基于 `on`
// 所以不再支持如 `v-on:keyup.enter` 修饰器
// 需要手动匹配 keyCode。
on: {
click: this.clickHandler
},
// 仅对于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// Scoped slots in the form of
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其他组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其他特殊顶层属性
key: 'myKey',
ref: 'myRef'
}