Appearance
渲染函数
渲染函数为你提供了完全的 JavaScript 编程能力来构建虚拟 DOM。在大多数情况下,Vue 推荐使用模板语法来创建应用。然而在某些使用场景下,你真的需要用到 JavaScript 完全的编程能力,这时渲染函数就派上用场了。
基本用法
渲染函数通过 h() 函数创建虚拟节点 (VNode):
javascript
import { h } from 'vue'
const vnode = h(
'div', // type
{ id: 'foo', class: 'bar' }, // props
[
/* children */
]
)h() 函数
h() 函数的名字来源于 hyperscript,意思是"能生成 HTML (超文本标记语言) 的 JavaScript"。这个名字来源于许多虚拟 DOM 实现默认形成的约定。
javascript
// 除了类型必填以外,其他的参数都是可选的
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', 'world')])声明渲染函数
组合式 API
在组合式 API 中,setup() 钩子可以返回一个渲染函数:
javascript
import { ref, h } from 'vue'
export default {
setup() {
const count = ref(1)
// 返回渲染函数
return () => h('div', count.value)
}
}选项式 API
在选项式 API 中,我们可以使用 render 选项:
javascript
import { h } from 'vue'
export default {
data() {
return {
msg: 'hello'
}
},
render() {
return h('div', this.msg)
}
}虚拟 DOM 树
Vue 组件树中的每个组件都有一个对应的 VNode 实例,该实例负责描述如何渲染这个组件。
javascript
const vnode = h('div', {
id: 'app'
}, [
h('header', [
h('h1', 'My App')
]),
h('main', [
h('p', 'Hello World!')
])
])完整示例
让我们看一个完整的示例,创建一个动态标题组件:
javascript
import { h } from 'vue'
export default {
name: 'DynamicHeading',
props: {
level: {
type: Number,
required: true
},
text: {
type: String,
required: true
}
},
render() {
return h(
`h${this.level}`, // 标签名
{}, // props
this.text // children
)
}
}使用这个组件:
vue
<template>
<DynamicHeading :level="1" text="Hello World!" />
<DynamicHeading :level="3" text="Subtitle" />
</template>渲染插槽
在渲染函数中访问插槽内容可以通过 this.$slots 来实现:
javascript
export default {
render() {
// `<div><slot></slot></div>`
return h('div', this.$slots.default())
}
}具名插槽:
javascript
export default {
render() {
// `<div><slot name="header"></slot><slot></slot><slot name="footer"></slot></div>`
return h('div', [
this.$slots.header(),
this.$slots.default(),
this.$slots.footer()
])
}
}作用域插槽:
javascript
export default {
render() {
return h('div', [
this.$slots.default({
text: 'hello'
})
])
}
}事件处理
在渲染函数中,事件监听器应该以 onXxx 的形式命名:
javascript
export default {
render() {
return h('button', {
onClick: this.handleClick
}, 'Click me!')
},
methods: {
handleClick() {
console.log('Button clicked!')
}
}
}事件修饰符:
javascript
export default {
render() {
return h('input', {
onKeyup: {
handler: this.handleKeyup,
modifiers: { enter: true }
}
})
}
}组件和 Props
渲染其他组件:
javascript
import MyComponent from './MyComponent.vue'
export default {
render() {
return h(MyComponent, {
// 传递 props
someProp: 'hello'
})
}
}条件渲染
在渲染函数中,你可以使用完整的 JavaScript 语法:
javascript
export default {
props: ['items'],
render() {
if (this.items.length) {
return h('ul', this.items.map(item => {
return h('li', { key: item.id }, item.name)
}))
} else {
return h('p', 'No items found.')
}
}
}v-model
在渲染函数中实现 v-model:
javascript
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
render() {
return h('input', {
value: this.modelValue,
onInput: (event) => {
this.$emit('update:modelValue', event.target.value)
}
})
}
}JSX
如果你更喜欢类似 HTML 的语法,可以使用 JSX:
jsx
export default {
render() {
const { level, text } = this
const Tag = `h${level}`
return <Tag>{text}</Tag>
}
}要使用 JSX,你需要安装 @vitejs/plugin-vue-jsx 插件:
bash
npm install @vitejs/plugin-vue-jsx -D然后在 vite.config.js 中配置:
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [vue(), vueJsx()]
})函数式组件
函数式组件是一种定义自身没有任何状态的组件的方式:
javascript
function MyComponent(props, { slots, emit, attrs }) {
return h('div', `Hello ${props.name}`)
}
// 添加 props
MyComponent.props = ['name']性能优化
静态提升
对于静态内容,Vue 编译器会自动进行静态提升:
javascript
// 编译器会将这个静态节点提升到渲染函数外部
const hoisted = h('div', 'Static content')
export default {
render() {
return h('div', [
hoisted, // 静态节点
h('div', this.dynamicContent) // 动态节点
])
}
}内联组件 Props
避免在每次渲染时创建新的对象:
javascript
// 不好
export default {
render() {
return h(MyComponent, {
style: { color: 'red' } // 每次都创建新对象
})
}
}
// 好
const staticStyle = { color: 'red' }
export default {
render() {
return h(MyComponent, {
style: staticStyle // 复用对象
})
}
}何时使用渲染函数
渲染函数适用于以下场景:
- 需要完全的编程能力:复杂的条件逻辑
- 动态组件名:根据数据动态选择组件
- 性能关键场景:避免模板编译开销
- 库和框架开发:提供更大的灵活性