渲染函数
-
- 1、什么是虚拟DOM
- [2、 render()函数的使用](#2、 render()函数的使用)
- 3、使用JavaScript代替模板功能
Vue.js使用了虚拟DOM机制,通过虚拟DOM来更新DOM节点,提高了渲染效率。在介绍组件时,组件模板是定义在选项对象的template选项中的,但是在一些场景中需要使用JavaScript来创建HTML,这时可以使用渲染函数(render()函数)。
1、什么是虚拟DOM
对元素和文本的操作,实际上就是对DOM节点的操作。将元素节点添加到当前的DOM树中,或者删除DOM树中的某个元素节点,都会引起浏览器对网页的重新渲染。随着单页应用程序的广泛应用,页面跳转和更新等操作都是在同一个页面中完成的,这样就会更加频繁地操作DOM,对网页的重新渲染就会比较耗时。为了解决DOM渲染效率的问题,Vue.js采用了虚拟DOM机制。
虚拟DOM并不是真正意义上的DOM。简单来说,虚拟DOM就是用JavaScript来模拟DOM。当DOM前后发生变化时,让JavaScript来对这种变化进行比较,比较之后,只更新需要改变的DOM,不需要改变的地方不处理,用这种方法来提高渲染效率。
虚拟DOM实际上是一个JavaScript对象。和正常的DOM对象相比,JavaScript对象更简单,处理速度更快,DOM树的结构可以很容易地用JavaScript对象来表示。
例如,在HTML中创建DOM结构,代码如下:
html
<ul id="menu">
<li class="item">电影</li>
<li class="item">音乐</li>
<li class="item">读书</li>
</ul>
将上述代码中的DOM树的结构用对应的JavaScript对象来表示,代码如下:
js
let ele = {
tag: 'ul',
props: {
id: 'menu'
},
children: [
{tag: 'li', props: {class: 'item'}, children: ['电影']},
{tag: 'li', props: {class: 'item'}, children: ['音乐']},
{tag: 'li', props: {class: 'item'}, children: ['读书']}
]
}
上述代码中,ele即创建的用于表示DOM树结构的JavaScript对象。使用JavaScript对象来表示DOM树的结构,当数据发生变化时直接修改这个JavaScript对象,然后将修改后和修改前的JavaScript对象进行对比,记录需要对页面执行的DOM操作,再将其应用到真正的DOM树,从而实现视图的更新。
2、 render()函数的使用
2.1、基本用法
假设要实现一个锚点的应用。锚点(anchor)是网页中超链接的一种。使用锚点可以在文档中设置标记,这些标记通常放在文档的特定主题位置或页面的顶部,然后可以创建到这些锚点的链接,这些链接可以帮助用户快速浏览指定位置。例如,生成两个带锚点的标题,代码如下:
html
<div id="app">
<h1>
<a href="#music">
音乐频道
</a>
</h1>
<h2>
<a href="#read">
读书频道
</a>
</h2>
</div>
<div style="margin-top: 300px;"></div>
<a id="music">
音乐频道
</a>
<div style="margin-top: 50px;"></div>
<a id="read">
读书频道
</a>
<div style="margin-top:1000px;"></div>
将这个功能封装为组件,代码如下:
html
<div id="app">
<anchor :level="1">
<a href="#music">音乐频道</a>
</anchor>
<anchor :level="2" title="read">
<a href="#read">读书频道</a>
</anchor>
</div>
<div style="margin-top:300px;"></div>
<a id="music">
音乐频道内容
</a>
<div style="margin-top: 50px;"></div>
<a id="read">
读书频道内容
</a>
<div style="margin-top:1000px;"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
});
vm.component('anchor', {
template: `
<div>
<h1 v-if="level === 1">
<a :href="'#' + title">
<slot></slot>
</a>
</h1>
<h2 v-if="level === 2">
<a :href="'#' + title">
<slot></slot>
</a>
</h2>
<h3 v-if="level === 3">
<a :href="'#' + title">
<slot></slot>
</a>
</h3>
<h4 v-if="level === 4">
<a :href="'#' + title">
<slot></slot>
</a>
</h4>
<h5 v-if="level === 5">
<a :href="'#' + title">
<slot></slot>
</a>
</h5>
<h6 v-if="level === 6">
<a :href="'#' + title">
<slot></slot>
</a>
</h6>
</div>`,
props: {
level: {
type: Number,
required: true
},
titel: {
type: String,
default: ''
}
}
})
vm.mount('#app');
</script>
上述代码中,考虑到标题元素(
~
)可以变化,将标题的级别定义成组件的Prop,根据传递的动态属性level可以在不同级别的标题中插入锚点元素。这样写的缺点是:组件的模板代码冗长,大部分代码都是重复的。
下面使用render()函数来改写上面的代码,代码如下:
html
<div id="app">
<anchor :level="1">
<a href="#music">音乐频道</a>
</anchor>
<anchor :level="2" title="read">
<a href="#read">读书频道</a>
</anchor>
</div>
<div style="margin-top:300px;"></div>
<a id="music">
音乐频道内容
</a>
<div style="margin-top: 50px;"></div>
<a id="read">
读书频道内容
</a>
<div style="margin-top:1000px;"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
});
vm.component('anchor', {
render(){
const {h} = Vue;
return h(
'h' + this.level,
{},
this.$slots.default()
)
},
props: {
level: {
type: Number,
required: true
},
title: {
type: String,
default: ''
}
}
})
vm.mount('#app');
</script>
上述代码中,使用render()函数创建虚拟DOM,通过拼接字符串('h' + this.level)的形式来构造标题元素,将组件的子节点存储在组件实例的$slots.default()中,这样就简化了代码。
2.2、h()函数
在render()函数中,使用h()函数可以构建虚拟DOM的模板,它返回的是一个JavaScript对象。这个对象所包含的信息会告诉Vue.js页面中需要渲染什么节点,包括其子节点的描述信息。这样的节点被称为虚拟节点(virtualnode,常简写为VNode)。虚拟DOM是由Vue组件树建立起来的整个VNode树的统称。
h()函数可以接收3个参数,示例代码如下:
js
h(
//第一个参数,必选
// {String | Object | Function}
//一个HTML标签,组件选项对象,或者是一个函数
'div',
//第二个参数,可选
//{ Object }
//一个包含模板相关属性的数据对象
{
},
//第三个参数,可选
// {String | Array}
//子虚拟节点,由h()函数构建
//或简单的使用字符串来生成的文本节点
[
'文本',
h('h1', 'Hello world'),
h(MyComponent, {
someProp: 'foobar'
})
]
)
- 第一个参数是必选的,它表示要创建的元素节点的名字或组件。它可以是一个HTML标签,也可以是一个组件选项对象,或者是一个函数。
- 第二个参数是可选的,它表示元素的属性集合。它是一个包含模板相关属性的数据对象,在template中使用。
- 第三个参数也是可选的,它表示子节点的信息。它可以是字符串文本,也可以是由h()函数构建的子虚拟节点。
在此之前,要想在模板中绑定样式或监听事件,就要在组件的标签上使用相应的指令。而在render()函数中,这些都写在包含属性的数据对象中。例如,定义一个绑定了样式且监听click事件的简单组件,当单击文本时为文本添加样式,使用template方式的代码如下:
html
<style>
.style{
width: 300px;
height: 100px;
line-height: 100px;
text-align: center;
font-size: 20px;
background: #6666FF;
color: #FFFFFF;
}
</style>
<div id="app">
<ele></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
});
vm.component('ele', {
template: `
<div id="demo"
:class="{style:isStyle}"
@click="addStyle">成功永远属于马上行动的人</div>`,
data: function() {
return {
isStyle: false
}
},
methods: {
addStyle: function() {
this.isStyle = true;
}
}
})
vm.mount('#app');
</script>


使用render()函数进行改写,代码如下:
html
<style>
.style{
width: 300px;
height: 100px;
line-height: 100px;
text-align: center;
font-size: 20px;
background: #6666FF;
color: #FFFFFF;
}
</style>
<div id="app">
<ele></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
});
vm.component('ele', {
render() {
return Vue.h(
'div',
{
class: {
'style': this.isStyle
},
id: 'demo',
onClick: this.addStyle
},
'成功永远属于马上行动的人'
)
},
data: function() {
return {
isStyle: false
}
},
methods: {
addStyle: function() {
this.isStyle = true;
}
}
})
vm.mount('#app');
</script>
虽然两种写法不同,但结果是相同的。由上例可知,使用template方式明显比使用render()函数方式可读性更好,代码更简洁。所以要在合适的情况下使用render()函数。
3、使用JavaScript代替模板功能
在template中可以使用Vue内置的一些指令实现某些功能,如v-if、v-for。但是在render()函数中无法使用这些指令,要实现某些功能可以使用原生JavaScript方式。
3.1、实现v-if功能
例如,在render()函数中使用原生if...else语句来判断输入的值是否为一个正整数,代码如下:
html
<div id="app">
请输入年龄:<input v-model="num" size="6">
<ele :num="num"></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
num: ''
}
}
});
vm.component('ele', {
render() {
//判断输入是否为正整数
if(!/^[1-9]*[1-9][0-9]*$/.test(this.num)) {
return Vue.h(
'p',
{style: {color: 'red'}},
'请输入正整数!'
)
}else {
return Vue.h(
'p',
{style: {color: 'green'}},
'输入正确!'
)
}
},
props: {
num: {
type: String,
required: true
}
}
})
vm.mount('#app');
</script>


示例:实现文本的放大和缩小。
在render()函数中使用原生JavaScript进行判断,根据判断结果实现文本的放大和缩小。单击"放大"按钮实现文本的放大效果,单击"缩小"按钮实现文本的缩小效果。代码如下:
html
<div id="app">
<ele :show="show">书读百遍,其义自现。</ele>
<button @click="show = !show">{{!show ? '放大' : '缩小'}}</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
show: false
}
}
});
vm.component('ele', {
render() {
if(this.show) {
return Vue.h(
'p',
{
style: {color: 'blue', fontSize: '26px'}
},
this.$slots.default()
)
} else {
return Vue.h(
'p',
{
style: {color: 'blue', fontSize: '16px'}
},
this.$slots.default()
)
}
},
props: {
show: {
type: Boolean,
default: false
}
}
})
vm.mount('#app');
</script>


3.2、实现v-for功能
在使用v-if的模板中可以通过原生JavaScript中的if和else语句实现逻辑判断。对于v-for指令,可以使用对应的for循环来实现。例如,在render()函数中使用for语句渲染一个列表,代码如下:
html
<div id="app">
<ele :items="items"></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
items: ['空山不见人,', '但闻人语响。', '返景入深林,', '复照青苔上']
}
}
});
vm.component('ele', {
render() {
let nodes = [];
for(let i = 0; i < this.items.length; i++) {
nodes.push(Vue.h(
'p',
this.items[i]
));
}
return Vue.h('div', nodes);
},
props: {
items: {
type: Array
}
}
})
vm.mount('#app');
</script>

3.3、实现v-model功能
在render()函数中也不能使用v-model指令。如果要实现相应的功能需要自己编写业务逻辑。例如,实现与v-model指令相同的功能,将单行文本框的值和组件中的属性值进行绑定。代码如下:
html
<div id="app">
<ele></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
});
vm.component('ele', {
render() {
let self = this;
return Vue.h(
'div',
[
Vue.h(
'input',
{
value: this.value,
oninput: (event)=>{
self.value = event.target.value;
}
}
),
Vue.h(
'p',
'输入的值:' + this.value
)
]
)
},
data() {
return {
value: ''
}
}
})
vm.mount('#app');
</script>
运行上述代码,当单行文本框中的内容发生变化时,value属性值也会相应进行更新。结果如图所示。

3.4、实现事件修饰符和按键修饰符功能
对于事件处理中的事件修饰符和按键修饰符,也需要使用对应的方法来实现。常用修饰符对应的实现方法如表所示。
| 修饰符 | 对应的实现方法 |
|---|---|
.stop |
event.stopPropagation() |
.prevent |
event.preventDefault() |
.self |
if(event.target !== event.currentTaget) return |
.enter |
if(event.target !== event.currentTarget) return |
.ctrl、.alt、.shift |
if(!event.ctrlKey) return |