new Vue({
el: '#app',
render: h => h(App)
})
复制代码
2021年了不会还有人对上面的代码不熟悉吧,通过脚手架构建的项目,main.js中都会有上面这些代码。
el是挂载的DOM元素,可以是DOM对象、选择器;
Render就是今天的大哥,如上是一个箭头函数,形参h也是一个函数,传入一个组件对象,将这个组件挂在到el上
createElement函数
这个函数就是上面Render函数的参数,运行后会返回一个虚拟DOM对象(Virtual Dom),最多传三个参数
createElement (el, ?description, ?VNode)
el 必选: { String | Object | Function }
一个HTML标签、组件选项或一个函数(用于函数化组件),例:"div"
description 可选: { Object }
一个对应属性的数据对象,作用于el参数上
具体选项如下:
{
// 动态绑定class, :class = { 'active': true }
'class': {
active: true
},
// 动态绑定style, :style = { 'color': 'red' }
'style': {
color: 'red'
},
// DOM元素的attribute属性, id = 'szgg', name = 'szgg'
'attrs': {
id: 'szgg',
name: 'szgg'
},
// 组件接收到的prop属性, 给子组件传值时使用
{
props: {
count: '0'
}
},
// DOM元素的property属性
domProps: {
innerHTML: '优雅永不过时'
},
// 事件监听器,修饰符不可用,需自己实现 @click=clickFn1
on: {
click: this.clickFn1
},
// 监听组件的原生事件,仅用于组件上,实际上还是使用$emit发送的事件 @click.native=clickFn2
nativeOn: {
click: this.clickFn2
},
// 自定义指令, v-my-directives:szgg.bar="1 + 1"
directives: [
{
name: 'my-directives', // 指令名
value: 2, // 指令的绑定值
expression: '1 + 1', // 指令的表达式
arg: 'szgg', // 指令的参数
modifiers: { // 指令的修饰符
'bar': true
}
}
],
// 作用域插槽, { name: props => VNode | Array<VNode> }
/**
<template #default="scope">
<span>{{scope.text}}</span>
</template>
*/
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'szgg',
// 其它特殊顶层 property
key: 'myKey',
// ref属性
ref: 'myRef',
// ref与v-for同时使用时,添加此属性,$refs.myRef会变成一个数组
refInFor: true
}
VNode 可选: { String | Array }
子节点,多个子节点就放入数组中传入
例:'西内' | ['无情铁手', '致残打击']
复制代码
实现一个点击功能,点击文字变色
点击后=>
// 挂载Vue实例,使用全局组件
<div id="app">
<ele></ele>
</div>
...
.active {
color: pink;
}
复制代码
// template属性写法
<script>
Vue.component('ele', {
template: `
<div id="szgg" :class="{'active': isActive}" ref="szgg" @click="clickFn">{{message}}</div>
`,
data(){
return {
isActive: true,
message: 'SZGG'
}
},
methods: {
clickFn () {
this.isActive = !this.isActive
}
}
});
new Vue({
el:'#app',
data:{
message:'HelloWorld'
}
})
</script>
复制代码
// render函数改写
<script>
Vue.component('ele', {
data(){
return {
isActive: true
}
},
render(createElement) {
return createElement('div', {
domProps: {
id: 'szgg'
},
class: {
'active': this.isActive
},
ref: 'szgg',
on: {
click: this.clickFn
}
}, [createElement('p', 'SZGG')])
},
methods: {
clickFn () {
this.isActive = !this.isActive
}
}
});
new Vue({
el:'#app'
})
</script>
复制代码
上面只是Render函数的初体验,我们可以使用它实现页面的逻辑,同时也可以用js模板实现v-if,v-for,v-model
v-if,v-for,v-model
我们使用逻辑实现以上三个指令,而不是写自定义的指令实现,有兴趣的可以自己写写
v-if在render函数中的实现
// v-if 的实现,就是if/else重写
<script>
Vue.component('ele', {
data(){
return {
isActive: true
}
},
render(createElement) {
if (this.isActive) {
return createElement('div', {
domProps: {
id: 'szgg'
},
class: {
'active': this.isActive
},
ref: 'szgg',
on: {
click: this.clickFn
}
}, [createElement('p', 'SZGG')])
} else {
return createElement('div', {
on: {
click: this.clickFn
}
}, '啥也没有')
}
},
methods: {
clickFn () {
this.isActive = !this.isActive
}
}
});
new Vue({
el:'#app'
})
</script>
复制代码
v-for在render函数中的实现
// v-for 的实现,就是数组的map方法重写
<script>
Vue.component('ele', {
data(){
return {
list: [1, 2, 3, 4, 5]
}
},
render(createElement) {
if (this.list.length) {
return createElement('ul', [this.list.map(i => createElement('li', i))])
} else {
return createElement('p', '你咋没数据呢')
}
}
});
new Vue({
el: '#app'
})
</script>
复制代码
v-model在render函数中的实现
<script>
Vue.component('ele', {
data(){
return {
value: '来了'
}
},
render(createElement) {
// 保存一下当前的this对象,即组件对象ele
let _this = this;
return createElement('input', {
domProps: {
value: _this.value
},
on: {
input(event){
_this.value = event.target.value
}
}
})
}
});
new Vue({
el: '#app'
})
</script>
复制代码
事件修饰符和按键修饰符
表1-1 部分事件修饰符和按键修饰符及对应的句柄
修饰符 | 对应句柄 |
---|---|
.stop | event.stopPropagation() |
.prevent | event.preventDefault() |
.self | if (event.target !== event.currentTarget) return |
.enter/.13 | if (event.keyCode !== 13) return |
.ctrl,.alt,.shift,.meta | if (!event.ctrlKey) return |
对于事件修饰符.capyure,.once,Vue提供了特殊的前缀
表1-2 事件修饰符.capture,.once的前缀
修饰符 | 前缀 |
---|---|
.capture | ! |
.once | ~ |
.once.capture | ~! |
修饰符有很多,这里我们只实现一个 .enter功能,在输入框里输入文字按enter打在公屏上,如下
<script>
Vue.component('ele', {
data(){
return {
value: '',
list: []
}
},
render(createElement) {
// 保存一下当前的this对象,即组件对象ele
let _this = this;
let listNode;
if (this.list.length) {
// 获取list列表中的VNodes
listNode = createElement('ul', this.list.map(i => createElement('li', i)))
} else {
listNode = createElement('p', '你快整点优雅的句子')
}
return createElement('div', [
createElement('input', {
attrs: {
placeholder: '请输入优雅的语句'
},
domProps: {
value: _this.value
},
on: {
input(event){
_this.value = event.target.value
},
// 监听键盘按下事件,是Enter的话再执行逻辑
keydown(event){
if (event.key !== 'Enter') return;
_this.list.push(event.target.value);
_this.value = ''
}
}
}),
listNode
])
}
});
new Vue({
el: '#app'
})
</script>
复制代码
render函数中的插槽
首先要先判断组件是否使用了插槽,即组件标签之间是否有内容,可以用$slots.default获取
默认插槽
<script>
Vue.component('ele', {
render(createElement) {
console.log(this.$slots.default);
if (this.$slots.default) {
return createElement('div', this.$slots.default)
} else {
return createElement('div', '你没用插槽啊')
}
}
});
new Vue({
el: '#app'
})
</script>
复制代码
作用域插槽
作用域插槽的实现有两个点:
1.在slot标签上绑定数据,即<slot :text="message"></slot>
2.在父组件中使用绑定的数据,即<child v-slot="props"><span>{{ props.text }}</span></child>
代码实现
<div id="app">
<ele>
<template #default="scope">
<span>{{scope.text}}</span>
</template>
</ele>
</div>
<script>
Vue.component('ele', {
data(){
return {
message: 'HelloWorld'
}
},
render(createElement) {
// 相当于`<div><slot :text="message"></slot></div>`,通过 this.$scopedSlots 访问作用域插槽,每个作用域插槽都是一个返回若干 VNode 的函数
return createElement('div', [
this.$scopedSlots.default({
text: this.message
})
])
}
});
new Vue({
el: '#app'
})
</script>
复制代码
上面的代码实现了第一步,通过测试我们可以在页面上显示出正确的内容,但是还是用template获取绑定的数据,下面我们再使用Render函数实现第二步。
<div id="app">
</div>
<script>
Vue.component('ele', {
data(){
return {
message: 'HelloWorld'
}
},
render(createElement) {
return createElement('div', [
this.$scopedSlots.default({
text: this.message
})
])
}
});
new Vue({
render(createElement) {
return createElement('div', [
// 相当于`<div><ele v-slot="props"><span>{{ props.text }}</span></ele></div>`
createElement('ele', {
scopedSlots: {
default: function (scope) {
return createElement('span', scope.text)
}
}
})
])
}
}).$mount('#app')
</script>
复制代码
上面代码为完成代码,使用Render函数实现了作用域插槽的功能
函数化组件
了解完上述信息,我们可以尝试用Render函数去描绘一个组件,在函数中定义组件的属性、行为、样式,那么这么做的好处是什么?
文章开头说到Render函数返回的是一个VNode,更容易进行渲染,如果我们把组件用函数化,就能提高渲染的效率,减少渲染开销
Vue.js中提供了一个属性functional
,设置为true时可以是组件无状态和无实例,也就是没有data和this上下文,那么我们如何获取数据呢?这时Render函数提供了第二个参数context来提供临时上下文,可以通过context来获取组件需要的data,props,slots,children,parent
属性, 例如:this.message
要改写为context.props.level
, this.$slots.default
要改写为context.children
下面看一个例子:
<div id="app">
<!--2. 给组件传入Vue实例中的数据-->
<ele :l="l"></ele>
<button @click="changeFn(1)">H1</button>
<button @click="changeFn(2)">H2</button>
<button @click="changeFn(3)">H3</button>
</div>
<script>
// 定义一个组件对项
let levelTitle = {
// 5. 数据最终传入函数化的组件
props: ['level'],
render(createElement) {
return createElement(`h${this.level}`, '我是' + this.level)
}
};
Vue.component('ele', {
// 函数化组件开启
functional: true,
render(createElement, context) {
return createElement(levelTitle, {
// 4. 拿到传入本组件的props,再传入组件ele
props: {
level: context.props.l
}
})
},
// 3. 组件中接受数据
props: {
l: {
type: Number
}
}
});
new Vue({
// 1. 定义初始化数据
data: {
l: 1
},
methods: {
changeFn(level){
this.l = level
}
}
}).$mount('#app')
</script>
复制代码
相信通过上面的例子应该对函数化组件有相应的了解了,也让我们折服在Render函数的魅力之下,但相比模块化的template来说,它的代码写起来太多,很不方便,所以函数化组件主要用于以下两个场景:
程序化的在多个组件中选择一个
在children,props,data传递给子组件之前操作他们
Render函数的学习告一段落,它给我们提供了另一种描绘组件的方法,让我们感受到js的强大之处,通过这些练习,我受益良多。