一、渲染函数
简例
<anchored-heading :level="1">Hello world!</anchored-heading>
const { createApp } = Vue
const app = createApp({})
app.component('anchored-heading', {
template: `
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
`,
props: {
level: {
type: Number,
required: true
}
}
})
我们要做一个锚点模板的时候,如此写的话会显得太过冗长,并且重复的东西太多,这时候可以尝试用 render 函数重写
const { createApp, h } = Vue
const app = createApp({})
app.component('anchored-heading', {
render() {
return h(
'h' + this.level, // 标签名
{}, // prop 或 attribute
this.$slots.default() // 包含其子节点的数组
)
},
props: {
level: {
type: Number,
required: true
}
}
})
从上面,我们可以看出,render 函数需要三个参数
// @returns {VNode}
h(
// {String | Object | Function} tag
// 一个 HTML 标签名、一个组件、一个异步组件、或
// 一个函数式组件。
//
// 必需的。
'div',
// {Object} props
// 与 attribute、prop 和事件相对应的对象。
// 这会在模板中用到。
//
// 可选的。
{},
// {String | Array | Object} children
// 子 VNodes, 使用 `h()` 构建,
// 或使用字符串获取 "文本 VNode" 或者
// 有插槽的对象。
//
// 可选的。
[
'Some text comes first.',
h('h1', 'A headline'),
h(MyComponent, {
someProp: 'foobar'
})
]
)
二、约束
1、VNodes 必须唯一
简例
// 不合法的
render() {
const myParagraphVNode = h('p', 'hi')
return h('div', [
// 错误 - 重复的 Vnode!
myParagraphVNode, myParagraphVNode
])
}
// 可以用工厂函数来实施
render() {
return h('div',
Array.from({ length: 20 }).map(() => {
return h('p', 'hi')
})
)
}
三、创建组件 VNode
如果要为组件创建 VNode,那第一个参数就应该是组件本身
// 组件创建 VNode,一般局部组件可以直接用
render() {
return h(ButtonCounter)
}
// 一般全局注册的组件需要使用 resolvComponent 去解析
const { h, resolveComponent } = Vue
// ...
render() {
const ButtonCounter = resolveComponent('ButtonCounter')
return h(ButtonCounter)
}
还有一些比较常用的代替
// v-if 和 v-for 用 if/else 和 map() 重写
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 则分为 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 ,以 click 为例,那么就是 onClick
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
})
}
// 对于插槽的使用,除了之前的例子以外,还可以用新的函数来包裹它
render() {
return h(
Panel,
null,
{
// 如果我们想传递一个槽函数,我们可以通过
header: this.$slots.header,
// 如果我们需要以某种方式对插槽进行操作,
// 那么我们需要用一个新的函数来包裹它
default: (props) => {
const children = this.$slots.default ? this.$slots.default(props) : []
return children.concat(h('div', 'Extra child'))
}
}
)
}
四、自定义指令
简例
// 可以使用 withDirectives 来使用自定义指令
const { h, resolveDirective, withDirectives } = Vue
// ...
// <div v-pin:top.animate="200"></div>
render () {
const pin = resolveDirective('pin')
return withDirectives(h('div'), [
[pin, 200, 'top', { animate: true }]
])
}
// resolveDirective 是模板内部用来解析指令名称的同一个函数。只有当你还没有直接访问指令的定义对象时,才需要这样做。
但个人建议如果到这种程度的情况下,可能还是不用这个会比较好,这样维护以及可读性都很差。
五、函数式组件
如果我们只是需要简单的显示,那么我们可以使用抛弃掉组件以及生命周期的函数式组件。它只接受两个参数,props 和 context。例
const FunctionalComponent = (props, context) => {
// ...
}
同时如果你将函数作为第一个参数传给 render h 的话,那么默认会将其当成函数式组件对待。