渲染函数
[TOC]
渲染函数
- 绝大多数情况 使用模板创建HTML
- 特定场景需要js 此时使用渲染函数render
DOM树
当浏览器读到代码时,会根据代码建立一个DOM节点树来追踪所有内容
- 每个元素都是一个节点
- 注释也是一个节点
- 每个节点都可以有孩子节点
- 高效更新所有节点比较困难
- 不需要手动
- 需要高速Vue 希望页面的HTML是什么 可以是在一个模板里或者一个渲染函数里
- 这两种情况 Vue都会自动保持页面的更新
虚拟DOM树
Vue通过建立一个虚拟DOM(VNode)来追踪如何改变真实DOM
- h() 是一个用于创建vnode的函数
参数
- tag {String | Object | Function | null} 一个HTML标签名、一个组件、一个异步组件或者null,使用null 会渲染成一个注释。==必须的==
- props {Object} 与attribute、prop和事件相对应的对象。 ==可选的==
children {String | Array | Object} 子VNode 使用
h()
构建 或者使用字符串获取文本Vnode或者有插槽的对象。==可选的==
完整实例
const { createApp, h } = Vue
const app = createApp({})
/** Recursively get text from children nodes */
function getChildrenTextContent(children) {
return children
.map(node => {
return typeof node.children === 'string'
? node.children
: Array.isArray(node.children)
? getChildrenTextContent(node.children)
: ''
})
.join('')
}
app.component('anchored-heading', {
render() {
// create kebab-case id from the text contents of the children
const headingId = getChildrenTextContent(this.$slots.default())
.toLowerCase()
.replace(/\W+/g, '-') // replace non-word characters with dash
.replace(/(^-|-$)/g, '') // remove leading and trailing dashes
return h('h' + this.level, [
h(
'a',
{
name: headingId,
href: '#' + headingId
},
this.$slots.default()
)
])
},
props: {
level: {
type: Number,
required: true
}
}
})
约束
VNodes 必须唯一
- 组件树中所有VNode必须是唯一的
需要重复很多次的元素/组件 使用工厂函数实现
render() { return h('div', Array.from({ length: 20 }).map(() => { return h('p', 'hi') }) ) } // 合法的渲染了20个相同的段落
使用JavaScript代替模板功能
v-if 和 v-for
<ul v-if="items.length">
<li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
利用渲染函数重写
props: ['items'],
render() {
if (this.items.length) {
return h('ul', this.items.map((item) => {
return h('li', item.name)
}))
} else {
return h('p', 'No items found.')
}
}
v-model
v-model扩展为
- modelValue
- onUpdate:modelValue
props: ['modelValue'],
emits: ['update:modelValue'],
render() {
return h(SomeComponent, {
modelValue: this.modelValue,
'onUpdate:modelValue': value => this.$emit('update:modelValue', value)
})
}
v-on
render() {
return h('div', {
onClick: $event => console.log('clicked', $event.target)
})
}
事件修饰符
- 对于
.passive
、.capture
和.once
事件修饰符,可以使用驼峰写法将他们拼接在事件名后面:
render() {
return h('input', {
onClickCapture: this.doThisInCapturingMode,
onKeyupOnce: this.doThisOnce,
onMouseoverOnceCapture: this.doThisOnceInCapturingMode
})
}
对于其他修饰符,在事件处理函数中使用事件方法
| 修饰符 | 处理函数中的等价操作 | | :--- | :--- | |
.stop
|event.stopPropagation()
| |.prevent
|event.preventDefault()
| |.self
|if (event.target !== event.currentTarget) return
| | 按键:.enter
,.13
|if (event.keyCode !== 13) return
(对于别的按键修饰符来说,可将 13 改为另一个按键码 | | 修饰键:.ctrl
,.alt
,.shift
,.meta
|if (!event.ctrlKey) return
(将ctrlKey
分别修改为altKey
,shiftKey
, 或metaKey
) |
插槽
通过
this.$slots
访问静态插槽内容,每个插槽都是一个VNode数组render() { // `<div><slot></slot></div>` return h('div', {}, this.$slots.default()) }
props: ['message'], render() { // `<div><slot :text="message"></slot></div>` return h('div', {}, this.$slots.default({ text: this.message })) }
要使用渲染函数将插槽传递给子组件,执行以下操作
const { h, resolveComponent } = Vue render() { // `<div><child v-slot="props"><span></span></child></div>` return h('div', [ h( resolveComponent('child'), {}, // pass `slots` as the children object // in the form of { name: props => VNode | Array<VNode> } { default: (props) => Vue.h('span', props.text) } ) ]) }
JSX
语法更接近于模板的语法 (Babel插件jsx-next 用于在Vue 中使用JSX语法)
import AnchoredHeading from './AnchoredHeading.vue' const app = createApp({ render() { return ( <AnchoredHeading level={1}> <span>Hello</span> world! </AnchoredHeading> ) } }) app.mount('#demo')