认识 Vue3 的模板
Q: Vue3 中的模板是什么?它和普通的 HTML 有什么区别?
A: 模板是 Vue 中构建视图的地方,写法基本上和 HTML 一模一样,上手无难度。但它之所以被称为「模板」,是因为提供了数据绑定、指令、表达式等不同于纯 HTML 的特性,让我们可以用声明式的方式将数据和 DOM 结构关联起来。
文本插值
模板中最基础的语法就是文本插值,使用一对大括号(又称双大括号、猫须语法),括号内部绑定动态数据:
<template>
<div>{{ name }}</div>
</template>
<script setup>
const name = 'Steve';
</script>
<style lang="scss" scoped></style>
当 name 的值发生变化时,页面上的内容也会自动更新。这是响应式系统在模板层面的最基本体现。
原始 HTML
有些时候,变量的值对应的是一段 HTML 代码,但是普通的文本插值只会将这段 HTML 代码原封不动地输出:
<template>
<div>{{ htmlCode }}</div>
</template>
<script setup>
const htmlCode = '<span style="color:red">this is a test</span>';
</script>
此时页面上会显示 <span style="color:red">this is a test</span> 这个字符串,而不会渲染成红色的文字。
如果想要让 HTML 字符串以 HTML 的形式渲染出来,就需要用到指令。指令是带有 v- 前缀的特殊属性,这里需要用到的指令是 v-html:
<template>
<div v-html="htmlCode"></div>
</template>
<script setup>
const htmlCode = '<span style="color:red">this is a test</span>';
</script>
注意:在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。请只对可信内容使用
v-html,绝不要对用户提供的内容使用。
属性绑定
Vue 的核心思想之一,就是将模板中所有的东西都通过数据来控制。除了普通文本以外,HTML 元素的属性也应该由数据来控制,这就是属性绑定。
使用 v-bind 指令来动态绑定属性:
<template>
<div v-bind:id="id">hello</div>
</template>
<script setup>
const id = 'my-id';
</script>
由于属性绑定使用频率非常高,Vue 提供了简写形式 —— 直接用一个冒号(:)表示该属性是动态绑定的:
<template>
<div :id="id">hello</div>
</template>
从 Vue 3.4 开始,如果动态绑定的属性名和数据名相同,还可以进一步简写:
<template>
<div :id>hello</div>
</template>
<script setup>
const id = 'my-id';
</script>
布尔类型属性
在 HTML 中,有一类比较特殊的属性 — 布尔类型属性,例如 disabled。对于这类属性,绑定的数据不同,会有不同的表现:
- 如果所绑定的数据是真值或者空字符串,该布尔属性会存在。
- 如果所绑定的数据是假值(
null和undefined),该布尔属性会被忽略。
<template>
<button :disabled="isDisabled">提交</button>
</template>
<script setup>
const isDisabled = true; // 按钮被禁用
</script>
批量绑定属性
如果要绑定多个属性,可以直接绑定成一个对象:
<template>
<div v-bind="attrObj">hello</div>
</template>
<script setup>
const attrObj = {
id: 'container',
class: 'wrapper'
};
</script>
这等价于:
<div id="container" class="wrapper">hello</div>
在模板中使用 JavaScript 表达式
到目前为止,模板只是将数据原样渲染。但在实际开发中,我们经常需要对要渲染的数据进行处理,Vue 支持在模板中使用 JavaScript 表达式:
<template>
<div>{{ number + 1 }}</div>
<div>{{ ok ? '晴天' : '雨天' }}</div>
<div>{{ message.split('').reverse().join('') }}</div>
<div :id="`list-${id}`">{{ id + 100 }}</div>
</template>
<script setup>
const number = 1;
const ok = true;
const message = 'hello';
const id = 1;
</script>
这里有一个关键点:要区分什么是表达式,什么是语句。
{{ var a = 1 }}
{{ if (ok) { return message } }}
有一个简单的判断方法:看是否能够写在 return 后面。如果能够写在 return 后面,那么就是表达式;如果不能,那么就是语句。
例如函数调用就是一个表达式:
return test();
所以在模板中调用函数也是合法的:
{{ test() }}
模板沙盒化
模板中可以使用表达式,但这些表达式都是沙盒化的。沙盒的意义主要在于安全——它限制了模板中能够访问到的全局对象。
Vue 内部维护了一个 全局对象白名单,只有在白名单中的全局对象才能在模板中访问。例如 Math、Date、parseInt 等常用的全局对象都在白名单中:
<template>
<div>{{ Math.random() }}</div>
</template>
但如果不在白名单中,则无法访问:
<template>
<div>{{ Test.a }}</div>
</template>
<script setup>
window.Test = { a: 1 };
</script>
上面这段代码会报错:Cannot read properties of undefined (reading 'a'),因为 Test 不在白名单中。
如果确实有需要在模板中访问自定义全局对象的需求,可以使用 app.config.globalProperties 来注册:
// main.js
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
// 注册全局属性
app.config.globalProperties.Test = {
a: 'Hello, Global Object!',
};
app.mount('#app');
这样,Test 就可以在所有组件的模板中直接访问了。
总结
Vue3 的模板语法在 HTML 的基础上提供了四个核心增强能力:
| 特性 | 语法 | 用途 |
|---|---|---|
| 文本插值 | {{ data }} |
动态渲染文本内容 |
| 原始 HTML | v-html |
渲染 HTML 字符串(注意 XSS 风险) |
| 属性绑定 | v-bind: / : |
动态绑定 HTML 属性 |
| JS 表达式 | {{ expression }} |
在模板中执行简单逻辑 |
理解模板语法是学习 Vue3 的第一步,掌握好这些基础,后面的组件、指令、响应式等高级特性才能水到渠成。