目录
前言
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'
}