☰
Vue3——第 3 篇:模板语法——从插值到指令的全面掌握
2026/10/19 3:12:42 网站建设 项目流程

本篇定位:模板语法是 Vue 的"视图层语言",是你每天编写 Vue 代码时打交道最多的部分。本篇将系统讲解 Vue 模板中的所有语法规则——从简单的文本插值到强大的指令系统,让你彻底掌握"如何把数据变成用户看到的界面"。读完本篇,你将能熟练阅读和编写任何 Vue 模板代码。


一、模板语法概览

Vue 使用一种基于 HTML 的模板语法,它允许你声明式地将组件实例的数据(JavaScript 状态)绑定到 DOM(页面结构)上。底层原理上,Vue 会将你的模板编译成高效的 JavaScript 渲染函数,并利用响应式系统在数据变化时智能地重新渲染。

简单来说,你只需要在模板中"声明"你想要的结果,Vue 会在背后自动完成所有 DOM 操作。

Vue 模板的两种编写方式:

方式说明适用场景
声明式模板(SFC 中的<template>)基于 HTML 扩展,包含指令和插值绝大多数场景,直观、易读、可维护性强
渲染函数(render/h函数)使用 JavaScript 编程式地描述 VNode需要高度动态或逻辑复杂的场景

本系列教程默认使用声明式模板,这也是 Vue 官方推荐的主流写法。

Vue 模板中的所有动态能力,都可以归结为两大类:

  1. 插值(Interpolation)—— 在 HTML 内容中嵌入动态数据
  2. 指令(Directives)—— 以v-开头的特殊属性,为 HTML 元素赋予动态行为

下面我们逐项深入学习。

二、文本插值:{{ }}Mustache 语法

最基本的数据绑定形式是文本插值,使用双大括号{{ }}(官方名称:Mustache 语法)。

<script setup lang="ts"> import { ref } from 'vue' const message = ref('Hello, Vue 3!') const count = ref(42) const user = ref({ name: '张三', age: 25 }) </script> <template> <div> <!-- 基本文本插值 --> <p>{{ message }}</p> <p>计数器当前值:{{ count }}</p> <!-- 访问对象属性 --> <p>姓名:{{ user.name }},年龄:{{ user.age }}</p> <!-- 插值中可以执行表达式(见后文详解) --> <p>明年年龄:{{ user.age + 1 }}</p> </div> </template>

核心特征:

  • {{ }}中的内容会被 Vue 解析为 JavaScript 表达式(不是语句)
  • 当绑定的数据发生变化时,插值内容会自动更新(响应式)
  • 插值只能用在文本节点中,不能用在 HTML 属性中(属性绑定需要用v-bind)

2.1 插值中的表达式规则

{{ }}中只能使用单个 JavaScript 表达式(Expression),不能使用语句(Statement)。

✅ 正确(表达式):

<p>{{ number + 1 }}</p> <p>{{ isActive ? '已激活' : '未激活' }}</p> <p>{{ message.split('').reverse().join('') }}</p> <p>{{ user.name.toUpperCase() }}</p>

❌ 错误(语句):

<!-- 赋值语句 --> <p>{{ let a = 1 }}</p> <!-- 流程控制 --> <p>{{ if (isActive) { return '激活' } }}</p> <!-- 声明变量 --> <p>{{ const message = 'hello' }}</p>

💡 设计原理:Vue 模板中的表达式在沙箱环境中执行,只能访问受限的全局变量列表(如Math、Date等),无法访问window对象。这意味着你无法在模板中调用window.alert()或访问自定义全局变量——这是出于安全考虑,强制你将逻辑放在组件代码中。

2.2 插值的 HTML 安全性

Vue 默认会对插值内容进行HTML 转义,以防止 XSS(跨站脚本攻击):

<script setup> const dangerousHtml = ref('<script>alert("恶意代码")<\/script>') </script> <template> <!-- 会被渲染为纯文本字符串,脚本不会执行 --> <p>{{ dangerousHtml }}</p> <!-- 输出:&lt;script&gt;alert("恶意代码")&lt;/script&gt; --> </template>

如果你明确知道内容是安全的 HTML 并希望直接渲染,需要使用v-html指令(见下文)。

三、原始 HTML:v-html指令

当你需要渲染包含 HTML 标签的字符串时,使用v-html指令:

<script setup lang="ts"> import { ref } from 'vue' const rawHtml = ref('<span style="color: red; font-weight: bold;">这是红色的加粗文字</span>') </script> <template> <div> <!-- 使用 v-html 渲染原始 HTML --> <div v-html="rawHtml"></div> <!-- 对比:使用插值只会显示纯文本 --> <div>{{ rawHtml }}</div> <!-- 输出:&lt;span style="color: red;"&gt;这是红色的加粗文字&lt;/span&gt; --> </div> </template>

⚠️ 安全警告:

  • v-html的内容会直接插入为 HTML,不会经过 Vue 的模板编译(即其中的{{ }}和指令不会被解析)
  • 永远不要在v-html中使用用户提供的内容(如 URL 参数、表单输入等),这会导致严重的 XSS 安全漏洞
  • 仅在内容完全可信(如你自己编写的富文本内容)时使用v-html

最佳实践:优先使用组件化渲染(如渲染函数或 VNode)替代v-html,这样既能保留 Vue 的响应式能力,又能保证安全。

四、属性绑定:v-bind指令

HTML 属性不能使用{{ }}插值,需要使用v-bind指令来动态绑定属性值。

4.1 基本用法

<script setup lang="ts"> import { ref } from 'vue' const dynamicId = ref('main-header') const imageUrl = ref('https://example.com/logo.png') const isDisabled = ref(true) const placeholderText = ref('请输入用户名') </script> <template> <!-- 完整写法:v-bind:属性名="表达式" --> <div v-bind:id="dynamicId">内容区域</div> <!-- 简写语法(最常用)::属性名="表达式" --> <img :src="imageUrl" alt="Logo" /> <input :disabled="isDisabled" :placeholder="placeholderText" /> </template>

渲染结果:

<divid="main-header">内容区域</div><imgsrc="https://example.com/logo.png"alt="Logo"/><inputdisabledplaceholder="请输入用户名"/>

4.2 动态绑定多个属性(对象语法)

如果你有一个包含多个属性的对象,可以使用v-bind的对象语法一次性全部绑定:

<script setup> import { ref } from 'vue' const attrs = ref({ id: 'submit-btn', class: 'btn primary', disabled: false, 'data-track': 'click-submit' // 带连字符的属性名需要用引号 }) </script> <template> <button v-bind="attrs">提交</button> </template>

渲染结果:

<buttonid="submit-btn"class="btn primary"data-track="click-submit">提交</button>

这在封装组件时非常有用(如透传未知属性给子组件)。

4.3 布尔类型属性的特殊处理

对于disabled、readonly、checked、selected等布尔属性,v-bind的行为与原生 HTML 一致:

  • 值为真值(true、非空字符串、对象等)时,属性被添加
  • 值为假值(false、null、undefined、空字符串)时,属性被移除
<script setup> const isActive = ref(true) const isEditable = ref(false) const emptyString = ref('') </script> <template> <button :disabled="isActive">禁用状态</button> <!-- 渲染:<button disabled>禁用状态</button> --> <input :readonly="isEditable" /> <!-- 渲染:<input /> (readonly 属性不存在) --> <input :disabled="emptyString" /> <!-- 渲染:<input disabled /> (空字符串是真值!) --> </template>

⚠️ 常见陷阱:空字符串''在 JavaScript 中是真值(truthy),所以:disabled="''"会添加disabled属性。如果要表示"不添加该属性",请使用null或undefined。

4.4 动态绑定 class 和 style(高频场景)

class和style是开发中最常绑定的属性,Vue 为它们提供了专门的增强语法。

绑定 class

① 对象语法:根据条件动态切换类名

<script setup> import { ref } from 'vue' const isActive = ref(true) const hasError = ref(false) const isLarge = ref(true) // 可以用计算属性或普通对象 const classObject = { active: true, 'text-danger': false, 'size-large': true } </script> <template> <!-- 单个类名动态切换 --> <div :class="{ active: isActive }">内容</div> <!-- 渲染:<div class="active">内容</div> --> <!-- 多个类名组合 --> <div :class="{ active: isActive, 'text-danger': hasError, 'size-large': isLarge }">状态组件</div> <!-- 渲染:<div class="active size-large">状态组件</div> --> <!-- 对象在代码中定义(推荐复杂逻辑时使用) --> <div :class="classObject">复杂状态</div> </template>

② 数组语法:绑定多个类名

<script setup> const isActive = ref(true) const activeClass = ref('active') const errorClass = ref('text-danger') const baseClass = 'btn' const isDisabled = ref(false) </script> <template> <div :class="[baseClass, activeClass, errorClass]">按钮</div> <!-- 渲染:<div class="btn active text-danger">按钮</div> --> <!-- 数组 + 对象混合 --> <div :class="[baseClass, { active: isActive, disabled: isDisabled }]">混合</div> </template>
绑定 style

① 对象语法:绑定内联样式

<script setup> const activeColor = ref('red') const fontSize = ref(16) const styleObject = ref({ color: 'blue', backgroundColor: '#f0f0f0', padding: '10px' }) const baseStyle = ref({ color: 'blue', backgroundColor: '#f0f0f0', padding: '10px' }) const overrideStyle = ref({ color: 'red', backgroundColor: '#f0f0f0', padding: '10px' }) </script> <template> <!-- 直接绑定属性 --> <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">文字</div> <!-- 绑定样式对象 --> <div :style="styleObject">样式对象</div> <!-- 数组语法:多个样式对象合并 --> <div :style="[baseStyle, overrideStyle]">合并样式</div> </template>

② 自动添加浏览器前缀:Vue 会自动检测并添加必要的浏览器前缀(如-webkit-、-moz-),你无需手动处理。

③ 多值语法(Vue 3.5+ 支持):

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

Vue 会按顺序尝试,直到浏览器支持为止。

五、JavaScript 表达式支持

Vue 模板中所有数据绑定位置({{ }}和指令的属性值)都支持完整的 JavaScript 表达式。

5.1 支持的表达式类型

<script setup> import { ref } from 'vue' const count = ref(10) const price = ref(99.9) const names = ref(['Alice', 'Bob', 'Charlie']) const user = ref({ firstName: '张', lastName: '三' }) const now = new Date() </script> <template> <!-- 算术运算 --> <p>{{ count + 5 }}</p> <!-- 三元运算符 --> <p>{{ count > 10 ? '大于10' : '小于等于10' }}</p> <!-- 方法调用(注意:每次渲染都会重新执行,慎用) --> <p>{{ price.toFixed(2) }}</p> <!-- 数组方法 --> <p>{{ names.join(' - ') }}</p> <!-- 对象解构 --> <p>{{ user.firstName + user.lastName }}</p> <!-- 逻辑运算 --> <div :class="[isActive && 'active', isDisabled || 'enabled']">逻辑</div> </template>

5.2 访问全局 API

Vue 模板中可访问的全局变量是受限的,主要包括:

  • Math、Date、Array、Object、String、Number、Boolean等原生构造器
  • Infinity、undefined、NaN、isFinite、isNaN、parseFloat、parseInt、decodeURI、decodeURIComponent、encodeURI、encodeURIComponent

如果需要访问window上的自定义全局变量,需要在组件中显式暴露。

5.3 设计哲学:为什么限制表达式复杂度?

Vue 推荐复杂逻辑放在计算属性或方法中,而不是写在模板里。原因有三:

  1. 可读性:模板应该清晰直观,一眼能看出 UI 结构
  2. 可测试性:计算属性和方法可以独立编写单元测试
  3. 性能:表达式在每次渲染时重新求值,复杂计算应该被缓存(computed)
<!-- ❌ 不推荐:模板中写复杂逻辑 --> <div>{{ userList.filter(u => u.age > 18).map(u => u.name).join(', ') }}</div> <!-- ✅ 推荐:逻辑放在计算属性中 --> <script setup> import { computed } from 'vue' const adultNames = computed(() => userList.value.filter(u => u.age > 18).map(u => u.name).join(', ') ) </script> <template> <div>{{ adultNames }}</div> </template>

六、指令系统概览

Vue 的指令(Directives)是带有v-前缀的特殊 HTML 属性。指令的职责是:当其绑定的表达式的值发生变化时,响应式地对 DOM 应用特定的行为。

6.1 指令的完整语法结构

v-指令名 :参数 .修饰符 = "表达式"
组成部分说明示例
指令名指令的核心标识v-on、v-bind、v-for
参数(可选)用冒号:指定v-bind:href→ 参数是href
修饰符(可选)用点.指定,用来改变指令行为v-on:click.stop→.stop修饰符阻止事件冒泡
表达式(部分指令可省略)指令的值v-if="isVisible"→ 表达式是isVisible

6.2 核心指令速查表

指令简写用途本篇涉及
v-bind:动态绑定 HTML 属性✅ 已讲
v-on@绑定事件监听器第 6 篇
v-model无双向绑定表单输入第 6 篇
v-if/v-else-if/v-else无条件渲染第 5 篇
v-show无基于 CSS 的显示/隐藏第 5 篇
v-for无列表渲染第 5 篇
v-html无渲染原始 HTML✅ 已讲
v-text无设置元素的 textContent(较少用,{{ }}更常用)本系列不单独展开
v-once无只渲染一次,后续不更新第 32 篇(性能优化)
v-memo无缓存渲染结果(Vue 3.2+)第 32 篇
v-pre无跳过编译,输出原始内容极少使用
v-cloak无编译前隐藏未编译的模板(CDN 场景)极少使用

本系列后续安排:v-on和v-model将在第 6 篇详解,v-if/v-show/v-for将在第 5 篇详解。

6.3 动态参数(Vue 3 新特性)

指令的参数可以是动态的,用方括号[]包裹:

<script setup> import { ref } from 'vue' const attributeName = ref('href') const url = ref('https://vuejs.org') const eventName = ref('click') </script> <template> <!-- 动态绑定属性名 --> <a :[attributeName]="url">Vue 官网</a> <!-- 等价于 :href="url" --> <!-- 动态事件名 --> <button @[eventName]="handleClick">点击</button> <!-- 等价于 @click="handleClick" --> </template>

动态参数的注意事项:

  • 动态参数表达式最终必须返回一个字符串(null表示移除该绑定)
  • 避免在动态参数中使用非字符串(如对象、数字等),Vue 会发出警告
  • 动态参数中不能使用空格和引号,如:[key + 'Value']是合法的,但:[key value]不合法

6.4 指令的优先级(深入理解)

当一个元素上同时存在多个指令时,Vue 按照以下优先级顺序处理:

v-pre > v-once > v-for > v-if/v-else-if/v-else > v-on > v-bind > v-model > v-show

实际影响:

  • v-for的优先级高于v-if,如果在同一元素上同时使用,v-if会在每次迭代中重复执行(性能浪费)。最佳实践:避免在同一元素上同时使用v-for和v-if,应使用计算属性过滤数据或将v-if放在外层容器上。
  • v-on的优先级高于v-bind,这意味着@click可以覆盖:click绑定(虽然基本不会这么做)。

七、综合示例:一个完整的组件模板

让我们综合运用本篇所学的所有语法,编写一个相对完整的组件模板:

<script setup lang="ts"> import { ref, computed } from 'vue' // 响应式数据 const user = ref({ name: '王小明', age: 28, email: 'wangxm@example.com', isActive: true }) const theme = ref('dark') const customClass = ref('highlight') const userAvatar = ref('https://example.com/avatar.jpg') const isExternal = ref(true) const dynamicEventName = ref('click') // 计算属性 const userDisplayName = computed(() => `${user.value.name}(${user.value.age}岁)` ) // 方法 function getUserCardStyle() { return { backgroundColor: theme.value === 'dark' ? '#1a1a2e' : '#f5f5f5', color: theme.value === 'dark' ? '#ffffff' : '#333333', padding: '20px', borderRadius: '12px' } } function handleAction() { console.log('点击了按钮') } </script> <template> <div :class="['user-card', { active: user.isActive, highlight: customClass }]" :style="getUserCardStyle()" v-bind="{'data-user-id': user.id || 'new'}" > <!-- 文本插值 + 表达式 --> <h2>{{ userDisplayName }}</h2> <!-- 属性绑定 --> <img :src="userAvatar" :alt="`${user.name}的头像`" class="avatar" /> <!-- 原始 HTML(仅在内容可信时使用) --> <div v-html="`<span class='badge'>${user.isActive ? '在线' : '离线'}</span>`"></div> <!-- 动态属性 --> <a :href="`mailto:${user.email}`" :target="isExternal ? '_blank' : '_self'"> {{ user.email }} </a> <!-- 动态参数(高级) --> <button :[dynamicEventName]="handleAction" class="action-btn"> 操作 </button> </div> </template> <style scoped> .user-card { transition: all 0.3s; } .user-card.active { border-left: 4px solid #4caf50; } .user-card.highlight { box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .avatar { width: 60px; height: 60px; border-radius: 50%; } .badge { background: #4caf50; color: white; padding: 2px 8px; border-radius: 12px; } </style>

八、本篇小结

知识点核心内容关键注意点
文本插值{{ expression }}只能写表达式,不能写语句;自动转义 HTML
原始 HTMLv-html="expression"⚠️ 有 XSS 风险,仅用于可信内容
属性绑定v-bind:attr="expr"简写:attr布尔属性用null/undefined移除,空字符串是真值
class 绑定对象语法:class="{ active: isActive }"和数组语法最常用的动态样式绑定方式
style 绑定对象语法:style="{ color: colorVar }"自动处理浏览器前缀
动态参数:[dynamicKey]="value"表达式必须返回字符串
指令系统v-*前缀,包含参数和修饰符优先级需了解,避免在v-for上同时用v-if

九、思考与练习

  1. 基础练习:在上一节创建的项目中,修改App.vue的模板,创建一个个人信息卡片。要求:

    • 使用ref定义姓名、年龄、职业、头像 URL
    • 使用插值显示姓名和年龄(年龄后加"岁")
    • 使用v-bind绑定头像src
    • 使用:class绑定一个名为card的样式类,使其居中、带阴影
  2. 陷阱排查:分析以下模板代码,指出所有潜在问题并给出修正方案:

    <template> <div> <p>{{ let username = 'admin' }}</p> <p>{{ userInfo }}</p> <div v-html="userInput"></div> <input :disabled="''" /> <div v-for="item in list" v-if="item.isActive"> {{ item.name }} </div> </div> </template>
  3. 动态参数实验:在 Vue 项目中尝试用动态参数绑定一个属性名,观察渲染结果。思考:如果动态参数的值是null会发生什么?

  4. 深度思考:为什么 Vue 模板中的表达式不能访问window对象?这种设计带来的利弊分别是什么?

下篇预告

第 4 篇我们将深入 Vue 的响应式系统——这是 Vue 最核心、最精髓的部分。你将学到:

  • ref和reactive的本质区别和选型建议
  • 为什么需要用.value访问ref的值
  • 模板中自动解包的规则
  • 响应式数据在 JavaScript 中如何触发视图更新

准备好揭开 Vue 响应式的神秘面纱!🎯

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询