render 函数的初步了解
我们通过下面的 demo 对 render 函数进行初步了解,其需求是传递不同的参数渲染不同的标签
组件实现
<div id="app">
<my-component :level="level">我是 demo</my-component>
</div>
<template id="demo">
<div>
<h1 v-if="level == 1">
<slot></slot>
</h1>
<h2 v-if="level == 2">
<slot></slot>
</h2>
<h3 v-if="level == 3">
<slot></slot>
</h3>
</div>
</template>
<script>
Vue.component('my-component', {
props: ['level'],
template: '#demo',
created: function() {
console.log(this.level)
}
})
var app = new Vue({
el: '#app',
data: {
level: 2
}
})
</script>
render 函数实现
<div id="app">
<render-component :level="level">我是 render</render-component>
</div>
<script>
Vue.component('render-component', {
props: ['level'],
render: function(createElement) {
return createElement('h' + this.level, this.$slots.default)
}
})
var app = new Vue({
el: '#app',
data: {
level: 2
}
})
</script>
显然可以看到组件需要很多代码的事情,render 函数可以简单做到,这对工程量较大的项目来说,着实是一个福利,避免了太多冗余代码的存在。
render 函数的第一个参数
在 render 函数的使用中,render 函数的参数必须是 createElement
,createElement
的类型是 function,第一个参数 (必选) 可以是 String | Object | Function。
Vue.component({
render: function(createElement) {
// 当参数为 String,为 html 标签
// return createElement('h1')
// 当参数为 Object,为一个含有数据选项的对象
// return createElement({
// template: '<div></div>'
// })
// 当参数为 Function,返回一个含有数据选项的对象
var func = function() {
return {
template: '<p>函数</p>'
}
}
return createElement(func())
}
})
render 函数的第二个参数
render 函数的第二个参数是可选的,是数据对象,只能是 Object。
看 demo 和 注释,这里列举 4 个选项
Vue.component('child', {
render: function(createElement) {
return createElement({
template: '<div>我是div</div>'
}, {
'class': {
div1: true, // 有 div1 这个 class
div2: false // 无
},
style: {
color: 'red',
fontSize: '20px'
},
// 正常的 html 特性
attrs: {
id: 'demo1', // id="demo1"
// src: 'https://www.baidu.com' img 等可以有这个特性
},
// 原生的 DOM 属性
domProps: {
innerHTML: '<span style="color: blue; font-size: 14px">我是span</span>'
}
})
}
})
render 函数的第三个参数
render 函数的第三个参数也是可选的,可以是 String | Array(用的多些),作为构建函数的子节点
Vue.component('third', {
render: function(createElement) {
return createElement('div', [
createElement('h1', '我是 h1'),
createElement('h4', '我是 h4')
])
}
})
上面的 demo 两次使用到了第三个参数,因为第二个参数只能是 Object 类型,所以传递的不是 Object 则默认没有传递第二个参数。
创建出的 DOM 结构为:
<div>
<h1>我是 h1</h1>
<h4>我是 h4</h4>
</div>
this.$slots 在 render 函数中的使用
<div id="app">
<slot-render>
<p>我是第一段</p>
<p>我是第二段</p>
<h1 slot="header">我是header标题</h1>
<span slot="footer">我是footer结尾</span>
</slot-render>
</div>
<script>
Vue.component('slot-render', {
render: function(createElement) {
var header = this.$slots.header
var main = this.$slots.default
var footer = this.$slots.footer
return createElement('div', [
createElement('header', header),
createElement('main', main),
createElement('footer', footer)
])
}
})
</script>
其中,this.$slots.header
返回的内容就是含有 VNODE 虚拟节点的数组,而方法 createElement('header', header)
返回的就是 VNODE,所以由此可知 render 函数的第三个参数存的就是 VNODE。
在使用 JS 操作 DOM 树时,会发生重绘;而 VNODE 虚拟节点在与页面双向绑定之后,VueJS 会自动检测哪个 VNODE 发生改变,就更新哪个 VNODE,效率比重绘高很多很多。
在 render 函数中使用 props 传递数据
<div id="app">
<p>通过 props 在render 函数中传递数据 {{level}}</p>
<button @click="switchImg">切换图片</button>
<switchimg :imgno="level"></switchimg>
</div>
<script>
Vue.component('switchimg', {
props: ['imgno'],
render: function(createElement) {
var imgsrc
if (this.imgno === 1) {
imgsrc = 'https://cn.bing.com/th?id=OIP.YAAQjUro7iesK4rOUHYVjgHaEo&pid=Api&rs=1'
} else {
imgsrc = 'https://cn.bing.com/th?id=OIP.3y5dDQmWRxPX1cOLmzZwIAHaEo&pid=Api&rs=1'
}
return createElement('img', {
attrs: {
src: imgsrc
},
style: {
width: '300px',
height: '200px'
}
})
}
})
var app = new Vue({
el: '#app',
data: {
level: 1
},
methods: {
switchImg: function() {
this.level = -this.level
}
}
})
</script>
v-model 在 render 函数中的使用
<div id="app">
<model-component v-model="msg"></model-component>
{{msg}}
</div>
Vue.component('model-component', {
props: ['msg'],
render: function(createElement) {
var self = this
console.log(this) // vue.component
return createElement('input', {
domProps: {
value: self.msg
},
on: {
input: function(event) {
console.log(this) // window
self.$emit('input', event.target.value)
}
}
})
}
})
var app = new Vue({
el: '#app',
data: {
msg: '这是 v-model 在 render 函数中使用的示例'
}
})
作用域插槽
通过 this.$scopedSlots.default 向父组件插槽传递数据
<slot-component>
<template slot-scope="props">
id: {{props.id}}
{{props.text}}
</template>
</slot-component>
Vue.component('slot-component',{
render: function(createElement) {
return createElement('p', this.$scopedSlots.default({
text: '我使来自子组件的数据',
id: 1
}))
}
})
函数化组件的应用
通过下面代码中两个 console.log(this)
可以表明函数化,拿到的都是 window;
context 代表的是上下文对象,包括子组件和父组件中的所有内容;
Vue.component('func-component', {
functional: true, // 函数化,表示当前组件无状态、无实例
props: ['msg'],
render: function(createElement, context) {
console.log(this) // window
return createElement('button', {
on: {
click: function() {
console.log(this) // window
console.log(context) // 上下文对象
console.log(context.parent) // 父组件
console.log(context.props.msg) // 父组件给子组件传递的数据 msg
}
}
}, "点击查看 context")
}
})
使用函数化之后的转变:
this.msg ----- context.props.msg
this.$slots.default ----- context.children // 默认插槽