目录
前言
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): VNodetype Children = string | number | boolean | VNode | null | Children[]type Slot = () => Childrentype 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`一样的 APIstyle: {color: 'red',fontSize: '14px'},// 正常的 HTML 特性attrs: {id: 'foo'},// 组件 propsprops: {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'
}