0%

Vue中模版和渲染函数的对比

做这个对比的意义

对于Vue开发,绝大多数情况都是使用模版template的方式来写渲染逻辑,但是通过官方文档我们知道有渲染函数render的存在。对于初学者,使用template能够快速上手,然而做一些高阶开发就用得上render 了。在render中,又有两种实现的途径,分别是基于createElement方法和JSX语法。

文档说render 更接近实际编译器。这个对比会让人对这句话有更直观的体会。另外对于模版的一些语法背后的实现也会有更深的理解。

本文会实现几项基本功能,每次都以template开始,接着用后面两种方式实现同样的功能。

render函数的参数是:createElement函数,为了简化也为了和bable工具统一,都写作了h

渲染基本数据

组件其它部分:

1
2
3
4
5
6
let vBindCom={
props:{
content:String,
styleObj:Object
},
}

template

1
2
template:
`<p :style="styleObj">{{content}}</p>`

###render:createElement

1
2
3
4
5
6
7
8
9
render:function(h){
return h(
'p',
{
style:this.styleObj
},
this.content
)
}

render:jsx

1
2
3
render:function(h){
return <p style={this.styleObj}>{this.content}</p>
}

v-html

组件其它部分:

1
2
3
props: {
htmlcontent: String
}

template

1
2
3
<template>
<p><span v-html="htmlcontent"></span> by template</p>
</template>

render:crateElement

1
2
3
4
5
6
7
8
9
10
render: function (h) {
return h("p", [
h("span", {
domProps: {
innerHTML: this.htmlcontent,
},
}),
" by render createElement",
]);
},

render:jsx

1
2
3
4
5
6
7
render: function (h) {
return (
<p>
<span domPropsInnerHTML={this.htmlcontent}></span> by render jsx
</p>
);
},

##v-if/v-else

组件其它部分:

1
2
3
props: {
openFlag: Boolean
}

template

1
2
3
4
5
6
7
<template>
<div>
<div v-if="openFlag">It's open</div>
<div v-else>It's closed</div>
<span>by template</span>
</div>
</template>

render:crateElement

1
2
3
4
5
6
7
8
9
10
render: function (h) {
let children = [];
if (this.openFlag) {
children.push(h("div", "It's open"));
} else {
children.push(h("div", "It's closed"));
}
children.push(h("span", "by render createElement"));
return h("div", children);
},

render:jsx

1
2
3
4
5
6
7
8
9
10
render: function (h) {
let children = [];
if (this.openFlag) {
children.push(<div>It's open</div>);
} else {
children.push(<div>It's closed</div>);
}
children.push(<span>by render jsx</span>);
return <div>{children}</div>;
},

v-model

组件其它部分:

1
2
3
props: {
value: Number
}

template

1
2
3
4
5
6
7
8
<template>
<label>
input a number:
<input type="number" @input="clickHander" :value="value" />
<span>输入值:{{ value }}</span>
<span> by template</span>
</label>
</template>

render:crateElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render: function (h) {
let children = [];
children.push("input a number:");
children.push(
h("input", {
attrs: {
type: "number",
},
domProps: {
value: this.value,
},
on: {
input: this.inputHander,
},
})
);
children.push(h("span", "输入值:" + this.value));
children.push(h("span", " by render createElement"));
return h("label", children);
},

这里需要注意:使用crateElment函数给input元素绑定值,使用了domProps属性,这和实现v-html时类似。

render:jsx

1
2
3
4
5
6
7
8
9
10
render: function (h) {
let children = [];
children.push("input a number:");
children.push(
<input value={this.value} onInput={this.inputHander} type="number" />
);
children.push(<span>输入值:{this.value}</span>);
children.push(<span> by render jsx</span>);
return h("label", children);
},

v-for

这个组件用到了另外一个组件,它负责把一条goods的信息渲染在一行(采用了Bootstrap的样式文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="row">
<div class="col-3 border">{{ item.name }}</div>
<div class="col-3 border text-end">{{ item.price }}</div>
<div class="col-3 border text-end">{{ item.num }}</div>
</div>
</template>
<script>
import { Goods } from "./vforBase";
export default {
props: {
item: Goods,// Goods是定义的一个类,它有三个属性:name、price、num
},
};
</script>

组件的其它部分:

1
2
3
4
5
6
7
8
import vforItem from "./vforItem";
...
props: {
items: Array
},
components: {
"vfor-item": vforItem
}

下面是循环渲染出所有goods的信息。

template

1
2
3
4
5
6
<template>
<div class="container-fluid">
<vfor-item v-for="(item, index) in items" :key="index" :item="item" />
<div class="text-start">by template</div>
</div>
</template>

render:crateElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
render: function (h) {
let childArr = [];
console.log(this.items.length);
for (let i = 0; i < this.items.length; i++) {
let item = this.items[i];
childArr.push(
h("vfor-item", {
props: {
item,
},
key: i,
})
);
}
childArr.push(
h(
"div",
{
class: "text-start",
},
"by render createElement"
)
);

return h(
"div",
{
class: "container-fluid",
},
childArr
);
},

render:jsx

1
2
3
4
5
6
7
8
9
render: function (h) {
let children = [];
for (let i = 0; i < this.items.length; i++) {
let item = this.items[i];
children.push(<vfor-item item={item} key={i}></vfor-item>);
}
children.push(<div class="text-start">by render jsx</div>);
return <div class="container-fluid">{children}</div>;
},

##v-slot

template

1
2
3
4
5
6
7
8
9
<template>
<div class="article">
This words is in component

<slot name="title" />
<slot />
<div class="by">by template</div>
</div>
</template>

render:crateElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render: function (h) {
return h(
"div",
{
class: "article",
},
[
"This words is in component",
this.$slots.title,
this.$slots.default,
h(
"div",
{
class: "by",
},
"by render createElement"
),
]
);
},

render:jsx

1
2
3
4
5
6
7
8
9
10
render: function (h) {
return (
<div class="article">
This words is in component
{this.$slots.title}
{this.$slots.default}
<div class="by">by render jsx</div>
</div>
);
},

scoped slot

组件的其它部分,组件内部定义了title:

1
2
3
4
5
data: function () {
return {
title: "Title string in component"
};
}

###template

1
2
3
4
5
6
7
<template>
<div class="article">
This words is in component
<slot name="title" v-bind:title="title">{{ title }}</slot>
<div class="by">by template</div>
</div>
</template>

render:crateElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
render: function (h) {
let titleContent = this.$scopedSlots.title
? this.$scopedSlots.title({ title: this.title })
: this.title;

return h(
"div",
{
class: "article",
},
[
"This words is in component",
titleContent,
h(
"div",
{
class: "by",
},
"by render createElement"
),
]
);
},

render:jsx

1
2
3
4
5
6
7
8
9
10
11
12
render: function (h) {
let titleContent = this.$scopedSlots.title
? this.$scopedSlots.title({ title: this.title })
: this.title;
return (
<div class="article">
This words is in component
{titleContent}
<div class="by">by render jsx</div>
</div>
);
},

filter

过滤器就是一个函数,大概定义成这样:

1
2
3
4
5
let myFilter = function (val) {
// console.log(val);
return val.toUpperCase();
};
export default myFilter;

组件的其它部分:

1
2
3
4
filters: { myFilter },
props: {
name: String
}

template

1
2
3
4
5
<template>
<span>
Welcom:<span>{{ name | myFilter }}</span>
</span>
</template>

render:createElement

1
2
3
4
5
6
render: function (h) {
return h("span", [
"Welcom:",
h("span", this.$options.filters.myFilter(this.name)),
]);
},

render:jsx

1
2
3
4
5
6
7
render: function (h) {
return (
<span>
Welcom:<span>{this.$options.filters.myFilter(this.name)}</span>
</span>
);
},

可见,在模版中使用管道|调用过滤器,在渲染函数中就变成了它本来的函数调用。

完整的实例

基本渲染

v-html

v-if

v-model

v-for

v-slot