MV-03 模板与渲染
03 · 模板与渲染(Template & Rendering)
📅 预计 75 分钟 | ⭐ = 高频考点 | 📌 中英术语见文末
✍️ 配套练习:网页练习shengxia.dev/quiz?module=mv03
1.0 先给直觉:模板 = 带空格的简历表
想象一张空白简历表:姓名、学校、技能这些格子是固定的,你只需要往里面填数据。填"小张",表格的"姓名"处就显示小张;换填"小李",自动变成小李。表格本身不变,变的只是数据。
Vue 的**模板(template)**就是这张表,**插值(interpolation)**就是那些"待填格子",数据一变,格子里的内容自动更新。你负责改数据,模板负责"填进去并保持同步"。
💡 记忆口诀:模板定框架,数据来填充,一变自动更。
1.1 插值语法:往格子里填数据 ⭐
双大括号 {{ }}
最基础的填法是把数据用 {{ }} 包起来,位置放哪都行,Vue 会把它替换成数据值:
<template>
<p>你好,{{ name }}!</p> <!-- 输出 name 的值 -->
<p>你今年 {{ age }} 岁。</p>
<p>1 + 1 = {{ 1 + 1 }}</p> <!-- 里面可以写简单表达式 -->
<p>{{ message.toUpperCase() }}</p> <!-- 甚至可以调用方法 -->
</template>
<script>
export default {
data() {
return {
name: '小张',
age: 20,
message: 'hello',
};
},
};
</script>
渲染结果:你好,小张!、你今年 20 岁。、1 + 1 = 2、HELLO。
要点:
{{ }}里可以放单个表达式:变量、运算、调用方法都可以。- 不能放完整的语句(如
if判断、for循环),表达式只求值、不执行逻辑。 - 数据变化时,对应插值自动重新渲染。
v-text 与 v-html
{{ }} 的替代写法,以及需要渲染 HTML 时的选择:
<p v-text="content"></p> <!-- 等价于 {{ content }},插入纯文本 -->
<p v-html="htmlContent"></p> <!-- 把字符串当 HTML 解析后插入 -->
注意:v-html 会真正执行 HTML 标签。如果内容来自用户输入,可能被注入恶意代码(XSS 攻击)。来自用户的内容绝不用 v-html,只对可信的静态 HTML 使用。
⚠️ 常见错误
{{ }}里写语句:{{ if (x) { ... } }}直接报错或显示异常,只能写表达式。- 变量名拼错:
{{ nam }}而 data 里是name,渲染为空白,不报错,很难发现。 - 滥用
v-html:把用户评论用v-html渲染,等于把安全问题留给自己。 - 在
{{ }}里做复杂计算:{{ list.filter(...).map(...) }}能跑但不清晰,应该用计算属性(后续讲义)。
1.2 指令概览:v- 前缀的命令
指令是什么
**指令(directive)**是以 v- 开头的特殊属性,告诉 Vue"对这个元素做什么"。它们像 HTML 自带属性,但 Vue 会解析并执行相应逻辑。
| 指令 | 作用 | 示例 |
|---|---|---|
v-bind |
单向绑定属性 | v-bind:href="url" |
v-on |
绑定事件 | v-on:click="handler" |
v-if / v-show |
条件渲染 | v-if="visible" |
v-for |
列表渲染 | v-for="item in list" |
v-model |
表单双向绑定 | v-model="username" |
v-text / v-html |
文本 / HTML 输出 | v-html="content" |
v-bind 与 v-on 的简写 ⭐
两个最高频的指令有简写,必须认识:
<a v-bind:href="url">链接</a> <!-- 完整写法 -->
<a :href="url">链接</a> <!-- 简写:冒号 -->
<button v-on:click="doSomething">按钮</button> <!-- 完整写法 -->
<button @click="doSomething">按钮</button> <!-- 简写:@ -->
:属性 绑定:把某个属性值与 data 数据关联,数据变属性跟着变。@事件 绑定:给元素挂事件处理。
<template>
<img :src="coverUrl" :alt="coverAlt" /> <!-- 动态图片地址 -->
<input :disabled="isDisabled" /> <!-- 动态禁用状态 -->
</template>
⚠️ 常见错误
- 忘记冒号/@ 简写规则:
href="url"会把字符串 "url" 当字面量,而不是变量值;要用:href="url"。 - 把指令当普通属性:
v-if="false"中 false 是 JS 布尔值,不是字符串,别加引号写成v-if="'false'"(那永远为真)。 - 事件处理写括号时机:
@click="add(1)"传参要括号,@click="add"不传参不写括号;都写在 data 里的方法名。
1.3 条件渲染:v-if 与 v-show ⭐
v-if / v-else-if / v-else
按条件决定是否渲染某个元素,条件不成立时元素根本不在 DOM 里:
<template>
<p v-if="score >= 90">优秀</p>
<p v-else-if="score >= 60">及格</p>
<p v-else>加油</p>
</template>
三个指令必须紧挨着,中间不能插入其他元素,否则 v-else 匹配不到前面的 v-if。条件为假时,元素不渲染(DOM 里没有)。
v-show:只是藏起来
v-show 通过 CSS 的 display: none 隐藏元素——元素始终在 DOM 里,只是看不见:
<p v-show="visible">我一直在 DOM 里,只是可能被藏起来</p>
怎么选 ⭐
- 频繁切换(如弹窗、菜单展开收起):用
v-show,只切 display,开销小。 - 很少切换、初始条件为假、或想省内存:用
v-if,完全不渲染更省。 - 附带注意:
v-if切换时会触发组件的创建/销毁(生命周期相关,见后续讲义),v-show不会。
⚠️ 常见错误
v-else与v-if之间有其他元素:中间隔着<br>或注释,导致v-else失效。- 高频切换用
v-if:每次都重建/销毁,开销大于v-show的 display 切换。 - 把
v-show用在很重的组件上:它一直存在,组件内部逻辑一直运行,要考虑代价。 - 混淆二者:面试常问"v-if 与 v-show 区别"——一个不渲染,一个 display:none。
1.4 列表渲染:v-for ⭐
遍历数组与对象
v-for 是渲染列表的核心指令,把数组每一项都"生成"一份模板:
<template>
<ul>
<li v-for="item in todos" :key="item.id">
{{ item.text }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
todos: [
{ id: 1, text: '买牛奶' },
{ id: 2, text: '写作业' },
{ id: 3, text: '锻炼' },
],
};
},
};
</script>
也可以拿到索引,并遍历对象:
<li v-for="(item, index) in todos" :key="item.id">
{{ index }} - {{ item.text }}
</li>
<!-- 遍历对象:key 是属性名,value 是值 -->
<div v-for="(value, key) in user" :key="key">{{ key }}: {{ value }}</div>
key:列表的"身份证" ⭐
:key 给每个列表项一个稳定且唯一的标识,让 Vue 能精确追踪哪一项变了、哪一项被删了。它直接决定列表更新效率和正确性。
- 用数据里的唯一 id(
item.id)最优。 - 不要用索引
index当 key:当列表发生插入/删除时,index 会"串位",导致复用错乱(比如输入框里的内容对错行)。
⚠️ 常见错误
- 忘记
:key:Vue 会警告,且更新时全量重建或复用错乱。 - 用
index当 key:列表顺序一变就出诡异 bug,尤其是包含表单的列表项。 v-for与v-if同层使用:v-if在v-for同元素上优先级难记,容易冲突。正确姿势:用计算属性先过滤,再v-for(见本讲 1.6)。- 遍历对象时 value/key 顺序写反:
(key, value)和(value, key)千万别记错,数组才有 index。
1.5 模板里的表达式与过滤器思想
简洁的插值表达式
模板里可以写简单表达式做轻量处理:
<p>{{ price * quantity }} 元</p>
<p>{{ isLoggedIn ? '欢迎回来' : '请登录' }}</p>
但复杂逻辑不该出现在模板里。模板应保持"易读",一旦表达式超过一两步,就应该抽出去,用计算属性(下一讲详解)处理。
v-cloak:防闪烁
初次加载时,模板可能短暂显示"原始双大括号"。用 v-cloak + CSS 隐藏未编译的模板:
[v-cloak] { display: none; } /* 未编译完成的模板先藏起来 */
<p v-cloak>{{ name }}</p>
网络慢时用户不会看到一闪而过的 {{ name }},提升体验。
⚠️ 常见错误
- 模板里写大量逻辑:又长又难读,改需求时要反复"翻译",维护成本高。
- 三元表达式滥用:嵌套三层以上,没人读得懂,抽出去更好。
- 忘了
v-cloak配 CSS:只写v-cloak属性不写 CSS,等于没加。
1.6 综合练习:带过滤的待办列表
用本讲知识做一个"只显示未完成项"的列表。这里先演示不推荐的写法(v-if 与 v-for 同层),再给出推荐写法:
<!-- ❌ 不推荐:v-if 与 v-for 同层 -->
<li v-for="item in todos" v-if="!item.done" :key="item.id">
{{ item.text }}
</li>
<!-- ✅ 推荐:先过滤,再渲染 -->
<template>
<ul>
<li v-for="item in unfinished" :key="item.id">{{ item.text }}</li>
</ul>
</template>
<script>
export default {
data() {
return {
todos: [
{ id: 1, text: '买牛奶', done: false },
{ id: 2, text: '写作业', done: true },
{ id: 3, text: '锻炼', done: false },
],
};
},
computed: {
unfinished() {
return this.todos.filter((item) => !item.done);
},
},
};
</script>
把"过滤逻辑"放到计算属性里,模板只剩干净的 v-for。这也是"模板只做展示、逻辑进 JS"的体现。
⚠️ 常见错误
- 过滤逻辑写进模板:模板变成"for 里套 if",可读性差,也容易在
v-if/v-for优先级上踩坑。 - 直接修改 props 或 data 的副本:
.filter返回新数组没问题,但直接用.splice原地改要格外小心。 - key 不稳:过滤后 index 会变化,务必用稳定 id 当 key。
1.7 样式绑定:让 class 和 style 也"活"起来
场景:状态决定样式
页面里经常要"根据数据给元素套不同的样式":选中高亮、按钮禁用、错误提示变红。除了用指令控制渲染,Vue 还提供了把 class / style 与数据绑定的能力。
:class 的三种写法
:class 可以绑定字符串、对象或数组,最常用的是对象语法——键是类名,值是布尔条件:
<template>
<!-- 对象语法:isActive 为真时加上 active 类 -->
<div :class="{ active: isActive }">内容</div>
<!-- 同时绑定静态类与动态类,互不冲突 -->
<div class="base" :class="{ active: isActive, error: hasError }">内容</div>
<!-- 数组语法:可组合多个类 -->
<div :class="['base', isActive ? 'active' : '']">内容</div>
</template>
对象语法最常用:"条件成立就加这个类",语义一目了然。
:style 绑定行内样式
:style 绑定一个对象,对象的属性名用驼峰写法(如 fontSize),适合少量、动态的样式:
<template>
<div :style="{ fontSize: size + 'px', color: fontColor }">文字</div>
</template>
<script>
export default {
data() {
return { size: 16, fontColor: '#e8590c' };
},
};
</script>
用计算属性整理样式逻辑
当样式条件复杂时,把判断抽进计算属性,模板保持干净:
<template>
<div :class="itemClass">待办项</div>
</template>
<script>
export default {
data() {
return { done: false };
},
computed: {
itemClass() {
return { 'todo-item': true, 'todo-done': this.done };
},
},
};
</script>
为什么不直接写死 class
直接写死静态类,页面永远一个样;用数据驱动 class,样式成为"状态的投影",才能实现"数据一变,外观自动跟着变"——这正是 MVVM 思想在样式上的延伸。
⚠️ 常见错误
:class里忘写::写成class="{ active: isActive }",会把整串字符串当静态类名,完全不生效。- 对象语法的值写成字符串:
:class="{ active: 'isActive' }"中'isActive'是字符串永远为真,应写布尔表达式isActive。 :style属性名用短横线:{ 'font-size': ... }也能用但需加引号,驼峰fontSize更自然。- 样式逻辑全堆模板:条件多时模板又长又难读,抽到计算属性。
1.8 模板背后的渲染机制
模板不是"直接塞进页面"
你可能以为模板就是"字符串拼起来塞进 HTML"。实际上 Vue 会先把模板编译(compile)成一个渲染函数(render function),渲染函数再根据数据生成"虚拟 DOM",最后才更新真实页面。
模板 → 编译 → 渲染函数 → 虚拟 DOM → 真实 DOM
这个过程对使用者是透明的,但理解它能解释很多现象。
虚拟 DOM:为什么更新那么快
直接操作真实 DOM 很慢(浏览器要重排、重绘)。Vue 维护一棵虚拟 DOM(virtual DOM)——用普通 JS 对象描述页面结构,比真实 DOM 轻得多。数据变化时:
- 重新执行渲染函数,生成新的虚拟 DOM。
- 与上一次的虚拟 DOM 做对比(diff),找出差异。
- 只把有差异的部分更新到真实 DOM。
这就是"精准更新"的来源——不是整页重绘,而是只动该动的地方。这也是前面强调 :key 的原因:diff 对比时靠 key 判断"哪些是同一项",key 合理,对比才高效准确。
为什么模板里不能写复杂逻辑
渲染函数每次数据变化都会执行,模板里的复杂表达式会重复求值,影响性能;同时模板本应"一眼看懂",塞满逻辑没人维护。把逻辑放进计算属性,利用它的缓存,既快又清晰——性能与可读性同时保证。
一个判断技巧
看到"页面更新慢"或"卡顿",先想三件事:是不是模板里有重计算?列表 key 是否合理?是否做了不必要的全量更新? 多数性能问题都出在这三处。
⚠️ 常见错误
- 在模板里调用耗时的函数:每次渲染都执行,拖慢更新;应放计算属性或抽离。
- 不理解 diff 而轻视 key:key 用错会导致虚拟 DOM 对比错乱,产生难以复现的显示 bug。
- 把虚拟 DOM 想成"HTML 字符串":它是 JS 对象结构,理解这一点才能读懂渲染原理。
📌 记忆口诀
- 插值:
{{ 表达式 }}只填值,语句别进来。 - 指令:
v-bind改属性、v-on绑事件,简写:和@。 - 条件:
v-if不渲染,v-show只是藏;频繁切换用v-show。 - 列表:
v-for配:key,key 用 id 不用 index。 - 模板职责:只做展示,逻辑进 JS。
📌 双语术语表(本讲)
| 中文 | English | 记忆点 |
|---|---|---|
| 模板 | template | 带数据的 HTML 骨架 |
| 插值 | interpolation | {{ }} 填数据 |
| 指令 | directive | v- 开头的特殊属性 |
| 条件渲染 | conditional rendering | v-if / v-show |
| 列表渲染 | list rendering | v-for |
| 双向绑定 | two-way binding | v-model |
| 单向绑定 | one-way binding | v-bind |
| 事件绑定 | event binding | v-on / @ |
| 键 | key | 列表项的唯一标识 |
⭐ 本讲考点清单
- 插值
{{ }}:写表达式、不写语句、自动更新 v-text与v-html的区别及v-html的安全风险v-bind(:)与v-on(@)的完整写法与简写v-if/v-else-if/v-else的连用规则(必须紧挨)v-if与v-show的区别及选择原则v-for遍历数组(值/索引)与对象(值/键)的写法:key的作用、为何用 id 不用 index- 模板表达式保持简洁、逻辑交给计算属性
v-if与v-for不同层的正确姿势