今天又看了一遍vue文档中的渲染函数,理解的差不多了,这里记录下,方便查阅,平时写的template组件vue内部通过正则匹配还是会编译成一棵虚拟节点树,最后放到渲染函数中去执行。
我们先用一个slot插槽的例子引出渲染函数:
很简单,Hello组件根据父组件传递的level值来判断渲染h1还是h2或h3 。
// Hello.vue
<template>
<div class="hello">
<h1 v-if="level===1"><slot></slot></h1>
<h2 v-else-if="level===2"><slot></slot></h2>
<h3 v-else="level===3"><slot></slot></h3>
</div>
</template>
<script>
export default {
name: "Hello",
props: {
level: Number,
require: true
}
}
</script>
// Home.vue
<template>
<div>
<hello :level="1">
hello world
<span>span标签</span>
</hello>
</div>
</template>
<script>
import Hello from '../components/Hello'
export default {
name: "Home",
components: {
Hello
}
</script>
上边写了好几个slot,代码有一些冗余!下边使用渲染函数改造下:
简单几行代码搞定,代码很简洁
// Hello.vue
<script>
export default {
name: "Hello",
functional: true,
props: {
level: Number,require: true
},
render: function(createElement) {
return createElement(`h${this.level}`, {}, ['', ...this.$slots.default])
}
}
</script>
createElement参数:
参数一:String | Object | Function
最常用的是:标签或组件,如:'div'
参数二:一个模板中对应的数据对象,可选项,具体常用属性如下:
{
class: { foo: true, bar: false }, // 与 `v-bind:class` 的 API 相同
style: { color: 'red', fontSize: '14px' }, // 与 `v-bind:style` 的 API 相同
attrs: { id: 'foo' }, // 普通的 HTML attribute
props: { myProp: 'bar' }, // 组件 prop
on: { click: this.clickHandler }, // 点击事件
}
参数三:String | Array
如果是字符串,一定是标签里的文本节点,如果是数组,里边是子级虚拟节点 (VNodes),由 `createElement()` 构建而成。
理解刚开始的例子:
createElment函数中,参数一是标签;参数二,没有属性,直接给它一个空对象;参数三,一个数组,其中第一项是一个空字符串,表示创建的标签里边子元素最前边没有内容,第二项就是VNode了。
红框中的文本节点和元素节点,会传递到子组件中,
子组件render函数中,两个节点会被编译成虚拟子节点(VNodes),并且通过:this.$slots.default
可以获取到:
它是一个数组,
createElement返回什么:
下边是官方文档的解释,
createElement
到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription
,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
插槽:
(1)this.$slot
上边已经记录过,可以通过 this.$slot 访问静态插槽的内容(也就是默认插槽),每个插槽都是一个VNode数组:
export default {
functional: true,
render: function(createElement) {
console.log('$slot:', this.$slots.default)
return createElement('h1', {}, ['测试'])
},
data() {return{}}
}
(2)通过 this.$scopedSlots 访问作用于插槽,每个作用域插槽都是一个返回若干 VNode 的函数:
有什么用呢,我们可以通过 this.$scopedSlots.default() 传递参数,这样父级可以拿到该参数,进行处理,很方便!
// hello.vue
<script>
export default {
props: {
text: String,
def: String
},
render: function(createElement) {
return createElement('h1', {}, [
this.$scopedSlots.default({
msg: {text: this.text, def: this.def}
})
])
}
}
</script>
// home.vue
<template>
<div class="home">
<hello text="123" def="456" v-slot:default="{msg}">
hello world
{{msg}} // 会输出:{ "text": "123", "def": "456" }
</hello>
</div>
</template>
函数式组件
函数式组件在开发中经常用的一种方式。
上边渲染函数,很简单,只是通过props接收了一些参数而已, 没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。在这样的场景下,我们可以将组件标记为 functional:true,这意味它无状态 (没有响应式数据),也没有实例 (没有 this
上下文)。一个函数式组件就像这样 hello.vue :
// hello.vue
<script>
export default {
functional: true,
props: {}, // 该属性可省略,在 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。
render:function(createElement, ctx) {
console.log('props:', ctx.props) // {text: "123", def: "456"}
console.log('children:', ctx.children) // [VNode, VNode]
console.log('slots:', ctx.slots()) // {default:[VNode, VNode]}
// 注意这里是defualt,而不是default!!!
console.log('scopedSlots:', ctx.scopedSlots.defualt()) // [VNode, VNode]
console.log('data:', ctx.data) // {attrs: {text:"123", def: "456"}}
console.log('parent:', ctx.parent) // VueComponents 父组件home
console.log('listeners:', ctx.listeners) // {click:f()} 就是父组件log函数
console.log('injections:', ctx.injections) // undefined
return createElement('h2', {}, ctx.children)
}
}
</script>
// 父组件 home.vue
<template>
<div class="home">
<hello text="123" def="456" @click="log">
hello world
<span>span标签</span>
</hello>
</div>
</template>
<script>
export default {
methods: {
log() {}
}
}
</script>
组件需要的一切,可以通过ctx参数来获取、传递,ctx对象包含如下字段(下边的属性在上边例子中都有输出):
补充:
1、你也可以这样写,返回一个数组,数组中是标签:
参数一必须为h参数,否则无效或报错!
<script>
export default {
functional: true,
render: function(h, ctx) {
return [<div>123321321</div>]
}
}
</script>
页面渲染如下:
2、通过 ctx.scopedSlots,可以编写一个临时变量
// com.js
export default {
functional: true,
render: function(createElement, ctx) {
console.log(ctx.scopedSlots.defualt())
return ctx.scopedSlots.defualt && ctx.scopedSlots.defualt({msg:ctx.props})
}
}
// Home.vue
<template>
<div>
<com text="123" def="456">
<template v-slot:defualt="{msg}">
{{msg}} // 可以在这里边使用 { "text": "123", "def": "456" }
</template>
</com>
</div>
</template>
<script>
import com from './com.js'
export default {
name: "Home",
components: {com}
}
<script>