前面我们的组件的模板都是在模板里写的(template),模板最后都会被vue编译成virtual dom(虚拟dom),在某些情况下模板可能不好用,例如需要实现一个动态的文章标题,根据父组件的level属性,动态的渲染成h1~hx标签,用模板写部分代码如下。
<article-header :level="1">Hello worldarticle-header>
<script type="text/x-template" id="article-header-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>
script>
<script>
Vue.component('article-header', {
template: '#article-header-template',
props: {
level: {
type: Number,
required: true
}
}
})
script>
代码写的很死板,不灵活。这时候使用render函数会变得非常的方便。改写如下
<html>
<head>
<meta charset="utf-8" />
<title>title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id="app">
<article-title :level="level">
hello world
title>
div>
<script>
Vue.component("article-title",
{
render:function(createElement){
return createElement(
'h'+this.level,
{
style:{
color:'red'
}
},
this.$slots.default)
},
props:{
level:{
type:Number,
required:true
}
},
data:function(){
return {
}
}
});
var app11 = new Vue({
el:'#app',
data:{
level:1
}
})
script>
body>
html>
其实render函数返回的createElement就是一个虚拟dom,来看下广告对createElement参数描述
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中属性对应的数据对象。可选。
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML 特性
attrs: {
id: 'foo'
},
// 组件 prop
props: {
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
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层属性
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
render函数内返回的createElement函数的第一个参数必填,一般是标签名,第二个参数,数据对象可选参数,第三个为子级虚拟节点。
所有组件树中,如果VNode是组件或含有组件的slot,那么Vnode必须唯一,例如。
<html>
<head>
<meta charset="utf-8" />
<title>title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id='app'>
<ele>ele>
<div>
<script>
var Child = {
render: function (createElement) {
return createElement("p", "text");
}
};
Vue.component("ele", {
render: function (createElement) {
var ChildNode = createElement(Child);
return createElement("div", [
ChildNode ,
ChildNode
]);
}
})
var app11 = new Vue({
el:'#app',
data:{
}
})
script>
body>
html>
简单的渲染看上去没问题,但是如果使用中涉及到其他复杂的特性,就可能会出问题,
正确的渲染多个重复的组件
Vue.component("ele", {
render: function(createElement) {
return createElement("div",
Array.apply(null, {
length: 20
}).map(function() {
return createElement(child);
})
)
}
});
使用javascript代替某些模板指令,向v-if,v-for,v-model等指令在render函数中都无法使用,都需要自己用js实现相同的逻辑,这就是深入底层的代价。
例如
<html>
<head>
<meta charset="utf-8" />
<title>title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id='app'>
<ele :type="type" :len="len">ele>
div>
<script>
var child = {
render: function(createElement) {
return createElement("p", "text");
}
};
Vue.component("ele", {
render: function(createElement) {
if(this.type == 1){
return createElement("div",
Array.apply(null, {
length: this.len
}).map(function() {
return createElement("p","one type");
})
);
}else{
return createElement("div",
Array.apply(null, {
length: this.len
}).map(function() {
return createElement("p","two type");
})
)
}
},
props:{
type:{
type:Number,
required:true,
default:1
},
len:{
type:Number,
required:true,
default:5
}
}
});
var app11 = new Vue({
el: '#app',
data: {
type:2,
len:6
}
})
script>
body>
html>
在render函数内,在父组件定义的插槽也是一个vnode,在render中可以通过this.$slots.插槽名去访问。
<html>
<head>
<meta charset="utf-8" />
<title>title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id="app">
<article-title>
<template v-slot:default>
hello world
template>
<template v-slot:nameslot="nameslot">
hello {{nameslot.name}}
template>
title>
div>
<script>
var child = {
render: function(createElement) {
return createElement('p', 'hello');
}
}
Vue.component("article-title", {
render: function(createElement) {
var node = createElement(child);
return createElement(
'div',
[node,
this.$slots.default,
//向作用域插槽传值
createElement('div', [
this.$scopedSlots.nameslot({
name: 'ly'
})
])
])
},
props: {
},
data: function() {
return {
}
}
});
var app11 = new Vue({
el: '#app',
data: {
}
})
script>
body>
html>
如果要用渲染函数向子组件中传递作用域插槽,可以利用 VNode 数据对象中的 scopedSlots 字段。
函数式组件是无状态的,无实例(没有this),入参是渲染上下文context,一切的参数都通过,context传递
属性名 | 作用 |
---|---|
props | 提供所有 prop 的对象 |
children | VNode 子节点的数组 |
slots | 一个函数,返回了包含所有插槽的对象 |
scopedSlots | (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通 |
data | 传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件 |
parent | 对父组件的引用 |
listeners | (2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。 |
injections | (2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的属性 |
组件定义时,有个functional属性设置为true就表示当前组件是一个函数式组件,render函数新增context上下文,属性如上表,返回的对象。获取默认插槽由this.$slots.default改为了context.children,context.children会返回所有子元素。
也可以用slots().default获取默认插槽,
<html>
<head>
<meta charset="utf-8" />
<title>title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id="app">
<article-title :level="level">
hello world
title>
div>
<script>
Vue.component("article-title",
{
functional:true,
render:function(createElement,context){
var props = context.props;
return createElement(
'h'+props.level,
context.data,
context.children)
},
props:{
level:{
type:Number,
required:true
}
},
data:function(){
return {
}
}
});
var app11 = new Vue({
el:'#app',
data:{
level:1
}
})
script>
body>
html>
参考
https://cn.vuejs.org/v2/guide/render-function.html#slots-和-children-对比