模板语法与指令
# 模板语法与指令
Vue 使用基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层组件实例的数据。本章介绍文本插值、属性绑定、条件渲染、列表渲染和样式绑定等核心指令。
# 核心语法
想学一门编程语言,就要先整明白它的语法规则。Vue 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层组件实例的数据。所有 Vue 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
# 1 文本插值
数据绑定最常见的形式就是使用双大括号 {{}} 的文本插值,如果你要用不常见的,那可用 <span v-text=""></span> 的方式。
在 Vue 中,带有 v- 前缀的特殊属性,称为指令。
无论选择哪一个插值方式绑定到组件(HTML标签)上,在组件实例上的值发生了改变,插值处的内容都会更新,这便是数据驱动的方式。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0核心语法</title>
<script src="vue.global.js"></script>
</head>
<body>
<div id="hello">
<input id="name" v-model="msg" placeholder="输入文本内容" />
<p>用双大括号实现文本插值:{{ msg }}</p>
<p>用v-text指定实现插值:<span v-text="msg"></span></p>
</div>
<script>
// 第1步:data数据;
// 第2步:vue实例,
// 第3步:绑定元素
Vue.createApp({
data() {
return { msg: '' }
}
}).mount("#hello")
</script>
</body>
</html>
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
# 2 原始 HTML
在开发过程中,有时会输出有格式的动态内容,比如输出带颜色文本。如果用插值的方式,输出的为文本内容,显然满足不了要求。这时就要用到 v-html 指令了。
温馨提醒:原始 HTML,尽量不要用在输入上,因为它很容易导致 XSS 攻击。
XSS 攻击通常指的是:通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页中。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0核心语法</title>
<script src="vue.global.js"></script>
</head>
<body>
<div id="vh">
<p>文本插值结果: {{ msg }}</p>
<p>原始HTML结果: <span v-html="msg"></span></p>
</div>
<script>
// 第1步:data数据;
// 第2步:vue实例,
// 第3步:绑定元素
Vue.createApp({
data() {
return {
msg: '<span style="color: red">vue.js 3.0</span>'
}
}
}).mount("#vh")
</script>
</body>
</html>
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
# 3 属性绑定
在 Vue 中,
- 对 HTML 属性的绑定,采用
v-bind(可缩写为":")指令, - 对事件的绑定,采用
v-on(可缩写为"@")指令。
这里说下 v-bind 的应用,v-on 在讲事件时再细讲。
你可能会奇怪,绑定属性有什么用?我随便举一个例子,你就秒懂了。
当美女程序员看我的文章时,系统就给她推荐有漂亮图表的文章链接地址;当男性程序员看我的文章时,系统就给推荐有漂亮MM的文章链接地址,以此来提高阅读量。
<!-- 完整语法 --> <a v-bind:href="url"> ... </a>
<!-- 缩写 --> <a :href="url"> ... </a>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0核心语法</title>
<script src="vue.global.js"></script>
</head>
<body>
<div id="vh">
<p>文本插值结果: {{ msg }}</p>
<p>原始HTML结果: <span v-html="msg" ></span></p>
<a :href="site">跳转</a>
</div>
<script>
// 第1步:data数据;
// 第2步:vue实例,
// 第3步:绑定元素
Vue.createApp({
data() {
return {
msg: '<span style="color: red">vue.js 3.0</span>',
site: 'http://www.sohu.com'
}
}
}).mount("#vh")
</script>
</body>
</html>
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
# 条件渲染和 template
所谓的条件,指的就是满足什么条件,允许做什么事,不满足时,是不允许做的。如共享单车,规定满 12 周岁或以上才可以骑行,没满的则不被允许。学每门编程语言,条件语句都是必须熟练掌握的,Vue3.0 的也不例外。
# 1 v-if 指令
在 Vue3.0 中,条件语句是通过 v-if 指令实现的。
如果判断条件为 True,就执行 v-if 语句里的内容,否则什么都不执行。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>vue3.0条件语法</title>
<script src="vue.global.js"></script>
</head>
<body>
<div id="app">
<h1 v-if='cool'>前端编程</h1>
</div>
<script>
Vue.createApp({
data() {
return { cool: true }
}
}).mount("#app")
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如果 v-if 为 false 时,你不想输出空气,不想什么都不执行,那就其后面加上 v-else 指令。
v-if 和 v-else 搭在一起使用,规则是:v-if 的条件为 true 时,执行 v-if 里的内容,否则执行 v-else 里的。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>vue3.0条件语法</title>
<script src="vue.global.js"></script>
</head>
<body>
<div id="app">
<h1 v-if='cool'>前端编程</h1>
<h1 v-else>不学前端,还可以学后端嘛</h1>
</div>
<script>
Vue.createApp({
data() {
return { cool: false }
}
}).mount("#app") </script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>vue3.0条件语法</title>
<script src="vue.global.js"></script>
</head>
<body>
<div id="app">
<h1 v-if="program === 'Vue3.0'">vue3.0 很酷,很酷 </h1>
<h1 v-else-if="program === '前端'">前端,很酷</h1>
<h1 v-else-if="program === 'Java'">Java,很酷</h1>
<h1 v-else-if="program === 'App'">App,很酷</h1>
<h1 v-else>Python,也酷</h1>
</div>
<script>
Vue.createApp({
data() {
return {
program: 'Vue3.0'
}
}
}).mount("#app")
</script>
</body>
</html>
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
# 2 template
长得有点(不是特别)酷的你,可能早就注意到了,v-if 指令,都是附加在一个元素上的;
如果想要实现切换多个元素,该如何?
可以将 v-if 放在 <template> 元素中。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>vue3.0条件语法</title>
<script src="vue.global.js"></script>
</head>
<body>
<div id="app">
<template v-if="show">
<h1>编程</h1>
<p>编程都用啥工具<br />
工具不重要,重要的是思想<br />
Python,前端(小程序)、Java和App 都是工具<br />
用啥都可以好程序</p>
</template>
</div>
<script>
Vue.createApp({
data() {
return {
show: true
}
}
}).mount("#app")
</script>
</body>
</html>
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
# 3 v-show 指令
- 如果你要实现业务方面的条件判断,用
v-if指令就可以了; - 如果只是想切换一下 CSS 的内容,那你可以用
v-show指令。
v-show 只是简单地切换元素的 CSS 属性值,不支持 v-else,也不支持 <template> 元素,它常用于隐藏或显示某个元素。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>vue3.0条件语法</title>
<script src="vue.global.js"></script>
<style>
.show {
margin-top: 20px;
width: 600px;
height: 50px;
background: darkcyan;
}
</style>
</head>
<body>
<div id="app">
<input type="button" value="按下就报幕" v-on:click="show"> <br />
<div class="show" v-show="report">报幕~~~</div>
</div>
<script>
Vue.createApp({
data() {
return {
report: false
}
},
methods: {
show() {
this.report = true;
}
}
}).mount("#app")
</script>
</body>
</html>
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
33
34
35
36
37
38
39
# 循环渲染
开发一个Web系统,如果没有涉及到列表功能的话,那真的有点奇葩了,而循环输出列表内容,不用for循环语句的话,那我真不知道该用什么了。在Vue3.0中,v-for指令基于一个数组来渲染输出一个列表内容。
# 1 v-for 输出项
Vue 的 v-for 指令使用 item in items 形式的特殊语法,同其他编程语言(如Python)并没有多大区别。
其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0循环语法</title>
<script src="vue.global.js"></script>
<style>
.lang {
border: 1px solid darkcyan;
width: 600px;
height: 200px;
}
</style>
</head>
<body>
<div class="lang">
Vue.js 3.0 系列教程
<ol id="program">
<li v-for="item in items">
{{ item.lang }}
</li>
</ol>
</div>
<script>
Vue.createApp({
data() {
return {
items: [{lang: 'Python'}, {lang: '前端'},
{lang: 'Vue3.0'}, {lang: 'koa2'},
{lang: 'Java'}, {lang: 'App'}]
}
}
}).mount("#program")
</script>
</body>
</html>
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
33
34
35
36
# 2 索引和值
数据列表在表格输出时,常常需要用到序号。
v-for 指令支持输出下标,将下标+1,即可获得序号。
为了更接近 JavaScript 迭代器的语法,Vue3.0 支持用 of 替代 in 作为分隔。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0循环语法</title>
<script src="vue.global.js"></script>
<style>
h1 {
text-align: center;
}
table {
width: 600px;
margin: 0 auto;
text-align: center;
height: 200px;
}
td, th {
border: 1px solid #bcbcbc;
padding: 5px 10px;
}
th {
background: #42b983;
font-size: 1.2rem;
font-weight: 400;
color: #fff;
cursor: pointer;
}
tr:nth-of-type(odd) {
background: #fff;
}
tr:nth-of-type(even) {
background: #eee;
}
</style>
</head>
<body>
<h1>vue.js 3.0 系列教程</h1>
<table id="program">
<thead>
<tr>
<th>序号</th>
<th>编程语言</th>
<th>备注</th>
</tr>
</thead>
<tr v-for="(item,index) of items">
<td>{{ index + 1 }}</td>
<td>{{ item.lang }}</td>
<td>{{ item.remark }}</td>
</tr>
</table>
<script>
Vue.createApp({
data() {
return {
items: [
{lang: 'Python', remark: '入门、数据分析、爬虫'},
{lang: '前端', remark: '网页三贱客'},
{lang: 'Vue3.0', remark: '能在项目中应用'},
{lang: 'Koa2', remark: '企业实战项目'},
{lang: 'Java', remark: '从0开始入门'},
{lang: 'App', remark: '安卓和iOS等开发'}]
}
}
}).mount("#program")
</script>
</body>
</html>
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 3 v-for 和 v-if
由于当 v-if 和 v-for 处于同一节点时,v-if 的优先级比 v-for 更高,这意味着 v-if 将没有权限访问 v-for 里的变量,就是说不可以将 v-if 和 v-for 放在同一个元素里。
<!-- v-for 和 v-if 不可以放在同一个元素内 -->
<li v-for="item in items" v-if="item==true">
{{ good }}
</li>
2
3
4
为了解决 v-for 和 v-if 同一水平的问题,你可以把 v-for 移动到
<template>标签中进行修正。就是将 v-if 放到 v-for 内部去的意思。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0循环语法</title>
<script src="vue.global.js"></script>
<style>
h1 {
text-align: center;
color: darkorange;
}
table {
width: 600px;
margin: 0 auto;
text-align: center;
height: 200px;
}
td, th {
border: 1px solid #bcbcbc;
padding: 5px 10px;
}
th {
background: #42b983;
font-size: 1.2rem;
font-weight: 400;
color: #fff;
cursor: pointer;
}
tr:nth-of-type(odd) {
background: #fff;
}
tr:nth-of-type(even) {
background: #eee;
}
</style>
</head>
<body>
<h1>Vue.js 3.0 系列教程</h1>
<table id="program">
<thead>
<tr>
<th>序号</th>
<th>编程语言</th>
<th>完成情况</th>
</tr>
</thead>
<template v-for="(item,index) in items">
<tr>
<td>
{{ index + 1 }}
</td>
<td>
{{ item.lang }}
</td>
<td>
<span v-if="item.todo==1">
已完成
</span>
<span v-else-if="item.todo==2">
进行中
</span>
<span v-else>
将启动
</span>
</td>
</tr>
</template>
</table>
<script>
Vue.createApp({
data() {
return {
items: [
{lang: 'Python', todo: 1,},
{lang: '前端', todo: 1},
{lang: 'Vue3.0', todo: 2},
{lang: 'Koa2', todo: 3},
{lang: 'Java', todo: 3},
{lang: 'App', todo: 3}]
}
}
}).mount("#program")
</script>
</body>
</html>
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 样式绑定
为了方便 CSS 对页面的美化,Vue3.0 特意提供了操作元素的 class 和内联样式的指令。
因为它们都是元素,所以我们可以用 v-bind(属性绑定) 进行处理。
由于字符串拼接麻烦且易错,所有 Vue3.0 在 v-bind 用于 class 和 style 时,做了专门的增强,表达式结果的类型除了字符串之外,还可以是对象或数组。
# 1 Class 指令
Vue 用 v-bind:class(简写为:class)指令,用于解决动态切换 class 的需求。
如果你不清楚用在什么哪方面的需求,就想一下提示信息的应用,在错误发生时,用红色字体,否则用绿色。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0 class指令</title>
<script src="vue.global.js"></script>
<style>
.b {
font-weight: bold;
}
.error {
color: red;
}
</style>
</head>
<body>
<span id="app">
<h1 class="b" :class="{ 'error': hasError }">红色字体,好看么?</h1>
</span>
<script>
Vue.createApp({
data() {
return {
hasError: true
}
}
}).mount("#app") // 挂载后成为组件
</script>
</body>
</html>
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
渲染结果(生成的代码)
<h1 class="b error">红色字体,好看么?</h1>
思考:如何通过单击按钮来切换颜色?
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0 class指令</title>
<script src="vue.global.js"></script>
<style>
.b {
font-weight: bold;
}
.error {
color: red;
}
</style>
</head>
<body>
<span id="app">
<button @click="change">改变颜色</button>
<h1 class="b" :class="{ 'error': hasError }">红色字体,好看么?</h1>
</span>
<script>
Vue.createApp({
data() {
return {
hasError: true
}
},
methods:{
change(){
this.hasError = !this.hasError
}
}
}).mount("#app")
</script>
</body>
</html>
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
33
34
35
36
37
如果你想同时满足多个 class,你可以用 class 的数组语法。这个语法,可读性更强一些。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0 class指令</title>
<script src="vue.global.js"></script>
<style>
.b {
font-weight: bold;
}
.big-text {
font-size: 60px;
}
.error {
color: red;
}
</style>
</head>
<body>
<span id="app">
<p class="b" :class="[fontSize, errorInfo]">
程序员<br/>
除了会编程之外,还会什么?<br/>
当然是敲代码了。
</p>
</span>
<script>
Vue.createApp({
data() {
return {
fontSize: 'big-text',
errorInfo: 'error'
}
}
}).mount("#app")
</script>
</body>
</html>
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
33
34
35
36
37
38
39
40
渲染结果(生成的代码)
<span id="app"><p class="b big-text error"> 程序员<br>
除了会编程之外,还会什么?<br> 当然是敲代码了。 </p></span>
2
你可以用三元表达式,根据条件切换列表中的 class。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0 class指令</title>
<script src="vue.global.js"></script>
<style>
.b {
font-weight: bold;
font-size: 50px;
}
.yes {
color: #008CBA;
}
.no {
color: red;
}
</style>
</head>
<body>
<span id="app">
<p class="b" :class="[isCool ? 'yes' :'no']">
蓝色字体才好看。
</p>
</span>
<script>
Vue.createApp({
data() {
return {
isCool: true
}
}
}).mount("#app")
</script>
</body>
</html>
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
33
34
35
36
37
38
# 2 内联样式
在 HTML 中,在元素上用 style 的话,结果是不可改变的,为此 Vue3.0 加上了:style 指令。
注意,你别看这个指令看起来十分直观,看着非常像 CSS,但其实是一个 JavaScript 对象来的。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue3.0 style指令</title>
<script src="vue.global.js"></script>
</head>
<body>
<span id="app">
<p :style="{ color: textColor, fontSize: fontSize + 'px' }">
程序员,都忙得跟狗一样,哈哈~
</p>
</span>
<script>
Vue.createApp({
data() {
return {
textColor: '#008CBA',
fontSize: 50
}
}
}).mount("#app")
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
你可以用样式对象的方式。这样的写法,是不是更舒服了一些,输出结果和上面是一模一样的。
<span id="app">
<p :style="styleOj">
程序员,都忙得跟狗一样都忙得跟狗一样,哈哈~
</p>
</span>
<script>
Vue.createApp({
data() {
return {
styleOj: {
color: '#008CBA',
fontSize: '50px'
}
}
}
}).mount("#app")
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
既然
class可以用数组语法,:style当然也可以。
:style 的数组语法可以将多个样式对象应用到同一个元素上。输出结果和上面的还是一样的。
<span id="app">
<p :style="[textColor, fontSize]">
程序员,都忙得跟狗一样,哈哈~
</p>
</span>
<script>
Vue.createApp({
data() {
return {
textColor: {
color: '#008CBA',
},
fontSize: {
'font-size': '50px'
}
}
}
}).mount("#app")
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Composition API 写法
以上是 Options API 的写法。在 Composition API + SFC 工程化项目中,属性绑定和条件列表渲染的写法如下。
# 属性绑定
<script setup lang="ts">
import { ref } from 'vue'
const path = ref('/src/assets/vue.svg')
</script>
<template>
<img :src="path" alt="">
</template>
2
3
4
5
6
7
8
9
- 【:属性名】用来将标签属性与【响应式】变量绑定
# 条件与列表
首先,新增模型数据 src/model/Model8080.ts
export interface Student {
id: number;
name: 沉梦听雨
sex: string;
age: number;
}
// 如果 spring 错误,返回的对象格式
export interface SpringError {
timestamp: string,
status: number,
error: string,
message: string,
path: string
}
// 如果 spring 成功,返回 list 情况
export interface SpringList<T> {
data: T[],
message?: string,
code: number
}
// 如果 spring 成功,返回 page 情况
export interface SpringPage<T> {
data: { list: T[], total: number },
message?: string,
code: number
}
// 如果 spring 成功,返回 string 情况
export interface SpringString {
data: string,
message?: string,
code: number
}
import { AxiosResponse } from 'axios'
export interface AxiosRespError extends AxiosResponse<SpringError> { }
export interface AxiosRespList<T> extends AxiosResponse<SpringList<T>> { }
export interface AxiosRespPage<T> extends AxiosResponse<SpringPage<T>> { }
export interface AxiosRespString extends AxiosResponse<SpringString> { }
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
33
34
35
36
37
38
39
40
41
42
其中
- AxiosRespPage 代表分页时的响应类型
- AxiosRespList 代表返回集合时的响应类型
- AxiosRespString 代表返回字符串时的响应类型
- AxiosRespError 代表 Spring 出错时时的响应类型
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import axios from "../api/request";
import { Student, SpringList } from "../model/Model8080";
// 说明 students 数组类型为 Student[]
const students = ref<Student[]>([]);
async function getStudents() {
// 说明 resp.data 类型是 SpringList<Student>
const resp = await axios.get<SpringList<Student>>("/api/students");
console.log(resp.data.data);
students.value = resp.data.data;
}
onMounted(() => getStudents());
</script>
<template>
<div class="outer">
<div class="title">学生列表</div>
<div class="thead">
<div class="row bold">
<div class="col">编号</div>
<div class="col">姓名</div>
<div class="col">性别</div>
<div class="col">年龄</div>
</div>
</div>
<div class="tbody">
<div v-if="students.length === 0">暂无数据</div>
<template v-else>
<div class="row" v-for="s of students" :key="s.id">
<div class="col">{{ s.id }}</div>
<div class="col">{{ s.name }}</div>
<div class="col">{{ s.sex }}</div>
<div class="col">{{ s.age }}</div>
</div>
</template>
</div>
</div>
</template>
<style scoped>
.outer {
font-family: 华文行楷;
font-size: 20px;
width: 500px;
}
.title {
margin-bottom: 10px;
font-size: 30px;
color: #333;
text-align: center;
}
.row {
background-color: #fff;
display: flex;
justify-content: center;
}
.col {
border: 1px solid #f0f0f0;
width: 15%;
height: 35px;
text-align: center;
line-height: 35px;
}
.bold .col {
background-color: #f1f1f1;
}
</style>
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
- 加入泛型是为了更好的提示
- v-if 与 v-else 不能和 v-for 处于同一标签
- template 标签还有一个用途,就是用它少生成一层真正 html 代码
- 可以看到将结果封装为响应式数据还是比较繁琐的,后面会使用 useRequest 改进