主题
不同Vue实例挂载到不同元素与模板中的插值表达式(Lesson1 3/4)
本页对应 Lesson01,共 6 段视频,约 1 小时 20 分。
学习目标
- 能使用
v-bind及其简写:为 HTML 属性绑定数据,并准确区分属性值中的字符串、Vue 实例数据与动态属性名写法。 - 能使用
style绑定完成字符串、对象、数组三种写法,并通过浏览器效果验证样式是否正确生效。 - 能使用
class绑定完成字符串、对象、数组写法,并能用对象属性的布尔值控制指定类名是否生效。 - 能使用
v-on绑定事件,分别写出内联语句、方法名、调用方法并传参三种形式,能获取并使用事件对象。 - 能使用
v-model在表单元素上实现双向绑定,并能说明其与v-bind加input事件写法的对应关系。 - 能使用
v-model的.trim、.number、.lazy修饰符处理输入数据,并通过页面行为验证修饰效果。
知识点
这一节看似只是“复制两份 new Vue”,实际是在建立 Vue 的一个基本认识:一个页面可以同时存在多个 Vue 实例,每个实例负责自己挂载元素内部的那一块视图。下面按“为什么需要 → 怎么想 → 怎么做 → 易错点”拆开说明。
需求:同一页面出现两个独立数据区域
为什么需要:真实页面经常不是只有一个数据源。比如左侧是“one”区域,右侧是“two”区域,它们都显示 {{str}},但希望分别显示 one 和 two。如果只用一个实例、一份 data,两个位置就会拿到同一个值,无法各自独立。
怎么想:既然两个区域的数据不同,就不要强行塞进同一个实例;让两个区域各自拥有一个 Vue 实例,各自提供 data.str。转写稿里反复强调“它会去你的 Vue 实例里面找属性”,迁移到这里就是:#one 里的 {{str}} 找第一个实例的 str,#two 里的 {{str}} 找第二个实例的 str。
怎么做:准备两个容器 #one、#two,分别 new Vue({ el: "#one", data: { str: "one" } }) 和 new Vue({ el: "#two", data: { str: "two" } })。完整代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="one">
<h1>{{str}}</h1>
</div>
<div id="two">
<h1>{{str}}</h1>
</div>
</body>
<script>
// 同一个页面,支持多个实例挂载至不同元素中。
new Vue({
el:"#one",
data:{
str:"one"
}
})
new Vue({
el:"#two",
data:{
str:"two"
}
})
</script>
</html>易错点:两个 el 不要指向同一个元素;同一块 DOM 交给两个实例管理,职责会冲突。容器 id 要和 el 选择器一致,#one 不能写成 one。
挂载范围:el 决定实例接管哪一块 DOM
为什么需要:Vue 不是自动扫描全页面,而是通过 el 指定挂载点。理解挂载范围,才能知道插值表达式由谁编译。
怎么想:el: "#one" 表示第一个实例接管 #one 元素及其内部内容;#two 同理。两个 h1 虽然写法一样,但处在不同容器里,所以由不同实例负责。
怎么做:先保证 DOM 已经存在,再执行实例代码。笔记把 <script> 放在 </body> 后面,就是为了让浏览器先解析出 #one、#two,再执行 new Vue。如果脚本提前执行,可能找不到挂载元素。
易错点:el 是选择器字符串时要带 #;不要把 el 写成 data 里的普通字段;两个实例的挂载范围不要嵌套或重叠。
数据隔离:两个 data.str 互不影响
为什么需要:多个实例的核心价值是数据独立。第一个实例的 str 是 "one",第二个是 "two",它们各自渲染,不共享状态。
怎么想:每个 new Vue 都有独立的 data 对象。第一个实例的插值表达式只访问第一个实例的 str;第二个只访问第二个实例的 str。转写稿里讲属性绑定时说“相当于去实例当中找属性”,这里也是同一思路:插值表达式先在所属实例里找数据。
怎么做:分别声明 data: { str: "one" } 和 data: { str: "two" }。运行后,第一个 h1 显示 one,第二个 h1 显示 two。
易错点:不要以为两个实例会共享 str;也不要在一个实例的模板里访问另一个实例的数据。若确实需要共享,应使用其他通信或状态管理方式,而不是靠“同名变量”猜测。
调试过程:先看挂载点,再看插值结果
为什么需要:多个实例同时出现时,出错往往不是语法错,而是挂载点或数据归属错。
怎么想:按顺序排查——页面是否有两个对应 id 的容器;el 是否分别指向它们;两个 data.str 是否写在不同实例中;插值表达式是否写在对应容器内部。
怎么做:运行页面后观察两个 h1 是否分别显示 one、two。如果两个都显示空或原样 {{str}},先检查 Vue 是否加载成功、实例脚本是否执行;如果两个显示同一个值,检查是否误把两个容器交给了同一个实例,或复制代码时漏改 data.str。
易错点:{{str}} 必须写在被挂载容器内部;挂载容器外部的插值不会被该实例编译。两个实例代码是并列关系,不要误写成一个实例嵌套另一个实例。
小结:多实例的关键是“各管一块、各用各的数据”
为什么需要:这一节建立的是页面组织思维,而不是单纯记语法。
怎么想:先划分页面区域,再为每个区域建立实例;每个实例只关心自己的 el 和 data。
怎么做:记住模板——new Vue({ el: 选择器, data: { 数据 } }),需要几块独立区域就写几个实例,但每个实例的挂载点必须明确且不冲突。
易错点:多个实例不是“多份 Vue 库”,而是同一个 Vue 下创建多个实例;不要重复挂载同一元素,不要让选择器写错,更不要把某个实例的数据误认为全局数据。
插值表达式解决什么问题
为什么需要:模板里要显示Vue实例data中的数据,不能把数据写死在HTML里;数据一变,视图要能跟着变。
怎么想:把{{ }}当成模板中的“占位符”,里面写Vue实例能识别的属性或表达式;Vue编译时把它替换成计算结果。
怎么做:在#root里写<p>{{num}}</p>,num来自data;写{{"str"}}时带引号,表示字符串字面量。
易错点:{{ }}只能放在标签内容位置,不能直接写在HTML属性里。转写稿里先写普通img src能显示,改成src="{{imgSrc}}"无效,最后要用v-bind:src;这说明“文本插值”和“属性绑定”是两件事。双花括号里写num会去实例找属性,写"str"才是字符串。
基本类型与三元表达式
为什么需要:页面经常要显示数字、布尔值、字符串,以及根据条件显示“男/女”这类结果。
怎么想:{{ }}里可以放JS表达式,Vue会求值后渲染;字符串必须加引号,否则会被当成变量名。
怎么做:{{1}}显示数字,{{false}}显示布尔值,{{sex===1?"男":"女"}}按条件输出;{{str.split("").reverse().join("")}}直接做链式运算。
易错点:不要写{{if(sex===1){...}}},插值里是表达式不是语句块;变量名写错会报“属性未定义”。转写稿反复强调:双引号里套单引号、单引号里套双引号,本质是在区分“字符串”和“实例属性”,这个思维同样适用于插值。
函数调用与漏括号
为什么需要:有些展示结果要经过方法计算,不能只读一个静态属性。
怎么想:{{fn}}拿到的是函数本身,通常不是想要的结果;{{fn()}}才是调用后的返回值。
怎么做:在methods里定义fn(){ return this.str.split("").reverse().join(""); },模板写{{fn()}}。
易错点:漏写括号会把函数本身渲染出来;方法里的this指向Vue实例,所以能访问this.str。注意与事件绑定区分:插值里的fn()会立即求值,而@click="fn()"是事件触发时才调用。
数组、对象与null/undefined
为什么需要:实际数据不只有基本类型,还有数组、对象;同时要理解空值为什么不显示。
怎么想:Vue会把数组、对象转成字符串形式显示,但不会像React那样把数组展开成多个节点;null和undefined不输出任何内容。
怎么做:{{arr}}、{{obj}}直接渲染;{{null}}、{{undefined}}页面无输出。
易错点:不要以为{{arr}}会自动循环出<li>,循环要用v-for;对象能输出但不代表能直接当列表用。笔记总结“数组不支持直接展开、对象支持输出”要记住。
template包裹标签
为什么需要:有时多个元素需要同一层包裹,但不想额外生成一个真实div。
怎么想:<template>是模板包裹标签,本身不渲染成真实DOM。
怎么做:笔记中保留了<template></template>写法,实际使用时把多个元素放进去。
易错点:不要把它当成普通可见容器;本节代码里它是空标签,重点是知道Vue模板中可用template包裹。
完整代码保留
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="root">
<!--将Vue实例下的属性num进行渲染-->
<p>{{num}}</p>
<!--将Vue实例下的属性str进行渲染-->
<p>{{str}}</p>
<!--将字符串进行渲染-->
<p>{{"str"}}</p>
<!--将数字进行渲染-->
<p>{{1}}</p>
<!--将Vue实例下的属性bol进行渲染-->
<p>{{bol}}</p>
<!--将布尔值进行渲染-->
<p>{{false}}</p>
<!--将Vue实例下的属性fn进行渲染-->
<p>{{fn}}</p>
<!--将Vue实例下的fn函数运行的结果进行渲染-->
<p>{{fn()}}</p>
<!--将Vue实例下的属性arr进行渲染-->
<p>{{arr}}</p>
<!--将Vue实例下的属性obj进行渲染-->
<p>{{obj}}</p>
<p>{{null}}</p>
<p>{{undefined}}</p>
<p>{{sex===1?"男":"女"}}</p>
<p>{{str.split("").reverse().join("")}}</p>
<template></template>
</div>
</body>
<script>
// 总结1:插值表达式支持字符串,数字,布尔,对象,数组,方法
// 总结2:undefined,null不会进行任何输出(与React相同)
// 总结3:数组不支持直接展开(React支持)
// 总结4:支持输出对象(React不支持)
// 总结5:Vue中包裹标签为<template></template>
new Vue({
el:"#root",
data:{
num:1,
str:"师法魔级超",
bol:true,
arr:[1,2,3,4],
sex:1,
obj:{
userName:"zhangsan"
}
},
methods:{
fn(){
return this.str.split("").reverse().join("");
}
}
})
</script>
</html>指令总览:为什么需要指令
Vue 模板里如果只写静态属性,DOM 一旦需要跟着数据变化,就得手写 DOM 操作,既繁琐又容易漏。指令就是解决“数据变,DOM 跟着变”的声明式写法。它是带有 v- 前缀的特殊属性,值通常是单个 JavaScript 表达式(v-for 是例外)。指令分内置指令与自定义指令;组件是对标签的扩展,指令是对属性的扩展。
本节可配合极简插件使用:https://chrome.zzzmh.cn/#/index
v-bind:把数据绑定到属性
为什么需要:图片地址、宽度、链接等属性值来自 data 时,不能直接写 src="imgSrc",否则浏览器只把它当普通字符串。
怎么想:普通属性写死值;要让属性值来自 Vue 实例,就用 v-bind:属性名="表达式"。
怎么做:
html
<div id="root">
<img v-bind:src="imgSrc" v-bind:width="imgW">
</div>
<script>
new Vue({
el: '#root',
data: { imgSrc: '图片地址', imgW: 100 }
})
</script>v-bind: 可简写为 ::
html
<img :src="imgSrc" :width="imgW">如果希望把“字符串”直接绑定给属性,而不是找实例属性,要在表达式里再加引号:
html
<img :src="'固定地址'" width="100">易错点:src="imgSrc" 不会读取数据;v-bind:src="imgSrc" 中的 imgSrc 没在 data 中定义会报错;数字 100 与字符串 '100' 语义不同,按需选择。
动态属性名与简写
为什么需要:有时属性名本身也来自数据,例如宽度属性名、链接属性名需要动态决定。
怎么想:v-bind: 后写中括号,表示“属性名由表达式计算”。
怎么做:
html
<img :[attrName]="imgW" :src="imgSrc">若 attrName 为 width,等价于 :width="imgW"。
易错点:中括号里不要随意加引号;:[attrName] 是动态属性名,不是动态值。转写稿里反复强调:双引号里写属性名时,默认按实例属性解析;要字符串就再套单引号。
style 特殊:对象与数组
为什么需要:行内样式经常由数据控制,直接拼字符串不直观。
怎么想:style 绑定可以是字符串、对象、数组;对象最常用。
怎么做:
html
<p :style="{ background: 'yellow', color: 'red' }">样式</p>
<p :style="styleObj">样式</p>js
data: { styleObj: { background: 'yellow', color: 'red' } }数组写法可组合多个样式对象:
html
<p :style="[styleA, styleB]">样式</p>易错点:写 :style="background: yellow" 会按表达式解析而报错;对象属性名是 CSS 属性,值应是合法字符串;字符串、对象、数组效果可能相同,但对象更利于数据驱动。
class 特殊:对象布尔值最常用
为什么需要:类名切换是高频需求,如高亮、禁用、激活状态。
怎么想:class 可绑定字符串、数组、对象;对象写法中“属性名是类名,值是布尔值”,为 true 生效,为 false 无效。
怎么做:
html
<p :class="{ bg: isBg, cl: isCl }">文字</p>js
data: { isBg: true, isCl: false }数组写法:
html
<p :class="['bg', 'cl']">文字</p>易错点:对象里 bg 是类名,不要误写成变量;若写 :class="bg",bg 必须存在于实例中。转写稿强调:对象布尔值写法用得最多,后面练习会大量出现。
v-on:事件绑定
为什么需要:数据变化要靠用户操作触发,如点击按钮让计数加一。
怎么想:v-on:事件名 绑定事件,简写 @。值可写内联语句、方法名、方法调用。
怎么做:
html
<button v-on:click="count++">加一</button>
<button @click="add">加二</button>
<button @click="addNum(3)">加三</button>js
data: { count: 0 },
methods: {
add() { this.count += 2 },
addNum(n) { this.count += n },
getEvent(e) { console.log(e.target) }
}需要事件对象时可用 $event:
html
<button @click="getEvent($event)">事件对象</button>易错点:@click="add" 传方法名,Vue 会调用;@click="add()" 是调用表达式,不会在页面加载时立即执行,而是在点击时执行;方法内 this 指向 Vue 实例。
v-model:表单双向绑定
为什么需要:v-bind 只能数据到视图,是单向的;输入框内容变化时,数据不会自动更新。
怎么想:表单元素需要“视图变,数据也变”,用 v-model。它可理解为 :value 加 input 事件的简写。
怎么做:
html
<input v-model="str">
<h3>{{ str }}</h3>等价思路:
html
<input :value="str" @input="str = $event.target.value">易错点:v-model 要与表单元素结合,不能随便写在 div 上;它本质是双向绑定,不是所有元素都适用。
v-model 修饰符:trim、number、lazy
为什么需要:收集表单数据时常要去空格、保数字、减少频繁更新。
怎么想:修饰符写在指令后,用点连接。
怎么做:
html
<input v-model.trim="str">
<input v-model.number="a">
<input v-model.lazy="str">.trim:去除左右空格,中间空格不去。.number:把输入值按数字处理,避免1 + 2变成字符串拼接。.lazy:失去焦点后再更新数据,减少输入过程中的频繁渲染。
易错点:.trim 不能去掉中间空格;.number 不是强制所有非法输入变数字;.lazy 不是不更新,而是延迟到失焦等时机更新。
先看完整示例
本节笔记的完整代码如下,先运行一遍,重点观察 v-text、v-html 后面的四种写法,以及 <template> 那一行。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="root">
<h3>v-text</h3>
<p v-text="userName">{{str}}</p>
<p v-text='userName'>{{str}}</p>
<p v-text=userName>{{str}}</p>
<p v-text=`userName`>{{str}}</p>
<hr/>
<h3>v-html</h3>
<p v-html="userName">{{str}}</p>
<p v-html='userName'>{{str}}</p>
<p v-html=userName>{{str}}</p>
<p v-html=`userName`>{{str}}</p>
<template v-html="userName">123</template>
</div>
</body>
<script>
// 响应式:视图是通过数据进行驱动的(当数据发生改变,视图会根据所使用的数据的变化而变化)
// 响应式即是视图会响应最新的数据。
// v-text与v-html的共同特点:
// 1- 值是一个字符串
// 2- 该字符串会作为Vue实例的属性名,将对应的属性值在指令所在的标签中进行输出。
// 3- 会覆盖原有数据。
// 4- 属性值可以使用双引号,单引号
// 5- 属性值也可以省略双引号,单引号
// 6- 如果使用的是反引号会将包裹的值在标签中直接输出
// 7- 无法与template结合使用。
// v-text与v-html的区别:
// v-text不支持HTML标签而v-html支持。
new Vue({
el:"#root",
data:{
str:"我是一个字符串",
userName:"zhangsan"
}
})
</script>
</html>v-text:把实例属性当纯文本输出
为什么需要:有时我们不想在标签里写 {{ }},而是希望用指令直接指定“这个标签的内容来自哪个数据”。v-text 就是用来把 Vue 实例中的数据以纯文本形式塞进标签内容里。它也会体现响应式:data 里的值变了,视图跟着变。
怎么想:先判断 v-text 后面写的到底是什么。按笔记的结论,v-text="userName" 中的 userName 会作为 Vue 实例的属性名,去 data 中找同名属性,把它的值输出到标签里。外层双引号、单引号只是 HTML 属性定界;省略引号也可以。转写稿里讲 v-bind 时反复调试过“属性值里写名字,是去实例里找,不是字符串”,这个思路同样要迁移过来:先看表达式,不要被外层引号迷惑。
怎么做:在示例中写 <p v-text="userName">{{str}}</p>,页面显示 zhangsan,原来的 {{str}} 被覆盖,不会显示“我是一个字符串”。v-text='userName'、v-text=userName 结果一样。反引号写法 v-text=`userName` 按笔记结论会把包裹的值直接输出,因此显示的是 userName 字面量,而不是 zhangsan。
易错点:第一,v-text 会覆盖标签原有内容,不要以为插值还会保留。第二,v-text 不解析 HTML 标签,如果数据里带 <b>,它会当普通文本显示。第三,不要用外层单双引号判断“是不是字符串”,真正决定表达式结果的是 Vue 对属性值的解析。
v-html:把实例属性当 HTML 解析
为什么需要:当数据本身是一段 HTML 字符串,并且我们确实希望浏览器把它渲染成标签结构时,v-text 不够用,需要 v-html。笔记明确说:v-text 不支持 HTML 标签,而 v-html 支持。
怎么想:v-html 的取值规则与 v-text 类似:v-html="userName" 会去实例中找 userName,把对应值输出到标签内部;它同样会覆盖原有内容。区别只在“输出时是否按 HTML 解析”。当前示例的 userName 是 zhangsan,没有标签,所以 v-text 和 v-html 看起来一样;如果数据是带标签的字符串,两者才会出现差异。
怎么做:按示例写 <p v-html="userName">{{str}}</p>,显示 zhangsan,原插值被覆盖。单引号、省略引号写法同样按表达式规则找实例属性。反引号写法按笔记结论输出 userName 字面量。<template v-html="userName">123</template> 这一行要特别注意:笔记结论是 v-text/v-html 无法与 template 结合使用,不能按普通标签那样理解。
易错点:第一,v-html 不是“更高级的 v-text”,只有确实需要渲染 HTML 时才用。第二,它同样覆盖原有内容。第三,不要把它写到 <template> 上期待正常渲染;示例已经把这个限制列出来了。
共同规则与调试易错点
为什么需要:这四条 v-text 写法和四条 v-html 写法放在一起,不是为了凑数量,而是为了让我们分清“HTML 外层引号”和“Vue 表达式”的区别。转写稿里调试 v-bind 时,先写 src="{{imgSrc}}" 不生效,后来改用 v-bind:src;又试过宽度直接写 imgW 不生效,说明属性值里的名字会被当作表达式/实例属性处理,而不是天然就是字符串。这个调试过程正好帮助我们理解本节。
怎么想:看到 v-text="userName"、v-text='userName'、v-text=userName,先不要看外层是单引号还是双引号,而要看 Vue 拿到的表达式内容。它们最终都指向实例中的 userName。看到反引号,则要意识到它变成了模板字符串字面量,所以输出包裹的值。看到 {{str}},要意识到它会被指令覆盖。
怎么做:把示例运行后逐行对比:前三行 v-text 显示 zhangsan,第四行显示 userName;v-html 同理。再把 data.userName 改成带标签的字符串做一次观察,就能看出 v-text 与 v-html 的差别。注意不要改代码结构,只改数据值即可。
易错点:一是把“外层引号”误认为“字符串引号”;二是忽略反引号的特殊性;三是忘记指令会覆盖元素内容;四是把 v-html 与 <template> 混用;五是混淆 v-text 和 v-html 对 HTML 标签的处理。转写稿里的“微杠办的”就是 v-bind,“组串”就是字符串,“实业对象”就是实例对象,读稿时要注意这些同音转写,理解时以代码为准。
先看目标:条件渲染解决什么问题
这一节要解决的问题是:页面上某些内容是否出现,不能写死,而要根据数据状态决定。比如年龄 age 不同,页面显示“老年/中年/壮年……”;登录状态 isLogin 不同,页面显示欢迎语或账号密码输入框。
为什么需要:如果只用 v-if 之外的普通插值,所有分支都会写在页面上,只是内容变了,DOM 仍然存在;条件渲染要控制“这个元素到底渲不渲染”。
怎么想:先判断条件复杂程度。简单二选一可以用三元表达式;多分支可以先用方法返回字符串,再进阶为 v-if / v-else-if / v-else 指令。
怎么做:模板里写表达式或指令,data 提供状态,methods 提供计算逻辑。
易错点:v-if 的值是布尔表达式,不是普通字符串;v-else-if 必须跟 v-if 结合;v-else 必须跟 v-if 或 v-else-if 结合;它们所在元素之间不要插入其它元素。
插值表达式里的条件判断
笔记开头先用插值表达式演示了条件运算:
html
<p>{{isShow}}</p>
<p>{{isShow && "你好&&"}}</p>
<p>{{isShow || "你好||"}}</p>
<p>{{sex===1?"男":"女"}}</p>
<p>{{fn()}}</p>为什么需要:让初学者先理解“模板里可以写表达式”,为后面 v-if 的条件判断打基础。
怎么想:isShow && "你好&&" 表示 isShow 为真时返回后面的字符串;isShow || "你好||" 表示 isShow 为假时才返回后面的字符串;三元表达式适合二选一。
怎么做:把 isShow、sex 放进 data,模板中用 {{ }} 包住表达式;fn() 是 methods 中的函数,返回 sex===1?"男":"女"。
易错点:{{ }} 里写的是表达式,不是语句;不要把 v-if 和插值混用成“字符串判断”。转写稿里反复强调“双引号里相当于表达式/实例属性”的理解,放到这里就是:sex===1 会被求值,而不是原样显示。
方法返回字符串:多条件的第一种写法
js
getAgeStr(){
if(this.age>80) return "老年";
if(this.age>40) return "中年"
if(this.age>28) return "壮年"
if(this.age>18) return "青年"
if(this.age>12) return "少年"
if(this.age>6) return "儿童"
return "婴儿"
}模板中写:
html
<p>{{getAgeStr()}}</p>为什么需要:多分支如果都塞进三元表达式,可读性会变差。把判断放进方法,模板更干净。
怎么想:方法按年龄从大到小判断,命中即 return,最后兜底返回“婴儿”。
怎么做:data 中给 age,methods 中写 getAgeStr,模板调用 {{getAgeStr()}}。
易错点:判断顺序要从大到小,否则 age=100 可能先命中小条件;方法里要用 this.age 访问 data。
指令版多条件:v-if / v-else-if / v-else
html
<p v-if="age>80">老年</p>
<p v-else-if="age>40">中年</p>
<p v-else-if="age>28">壮年</p>
<p v-else-if="age>18">青年</p>
<p v-else-if="age>12">少年</p>
<p v-else-if="age>6">儿童</p>
<p v-else>婴儿</p>为什么需要:方法返回字符串仍然会渲染一个 <p>;如果希望不满足条件的元素根本不渲染,就要用 v-if 系列。
怎么想:从上到下判断,第一个成立的分支渲染,后面的不再判断;全部不成立时走 v-else。
怎么做:v-if 写第一个条件,后续用 v-else-if,最后用 v-else。笔记中 age:100,运行后只显示“老年”。如果把 age 改成 50,则显示“中年”。
易错点:v-else-if 和 v-else 不能单独使用;它们必须紧挨着前面的条件元素。笔记注意4说“指令所在的元素之间不要出现其它元素”,否则分支关系会被打断。
用 template 包裹一组元素
html
<template v-if="isLogin">您好,欢迎您的到来!</template>
<template v-else>
<input type="text" placeholder="请输入管理员账号">
<input type="text" placeholder="请输入管理员密码">
</template>为什么需要:有时一个条件要控制多个元素,如果都套在 <div> 里会多出一层 DOM;template 可以作为分组容器,不渲染自身。
怎么想:登录时显示欢迎语,未登录时显示账号密码输入框。两组内容分别放进 template,条件写在 template 上。
怎么做:data 中 isLogin:true,第一个 template 显示;改成 false 则显示第二个 template 内的两个输入框。
易错点:v-else 后面不写值;template 上的 v-if 与后面的 v-else 仍要连续,中间不要插其它元素。
完整代码与运行结果
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="root">
<p>{{isShow}}</p>
<p>{{isShow && "你好&&"}}</p>
<p>{{isShow || "你好||"}}</p>
<!-- 三元表达式 -->
<p>{{sex===1?"男":"女"}}</p>
<p>{{fn()}}</p>
<!--多条件1-->
<p>{{getAgeStr()}}</p>
<!--多条件2-使用指令-->
<p v-if="age>80">老年</p>
<p v-else-if="age>40">中年</p>
<p v-else-if="age>28">壮年</p>
<p v-else-if="age>18">青年</p>
<p v-else-if="age>12">少年</p>
<p v-else-if="age>6">儿童</p>
<p v-else>婴儿</p>
<hr/>
<template v-if="isLogin">您好,欢迎您的到来!</template>
<template v-else>
<input type="text" placeholder="请输入管理员账号">
<input type="text" placeholder="请输入管理员密码">
</template>
</div>
</body>
<script>
// 注意1:v-if以及v-else-if的值是一个布尔值,如果成立则渲染指令所在的元素,不成立则不渲染
// 注意2:v-else-if需要与v-if结合使用
// 注意3:v-else=需要与v-if或v-else结合使用
// 注意4:v-if,v-else-if,v-else指令所在的元素之间不要出现其它元素。
new Vue({
el:"#root",
data:{
isShow:true,
sex:1,
age:100,
isLogin:true
},
methods:{
fn(){
return this.sex===1?"男":"女"
},
getAgeStr(){
if(this.age>80) return "老年";
if(this.age>40) return "中年"
if(this.age>28) return "壮年"
if(this.age>18) return "青年"
if(this.age>12) return "少年"
if(this.age>6) return "儿童"
return "婴儿"
}
}
})
</script>
</html>易错点清单
v-if="age>80"中age>80是表达式,不要写成字符串"age>80"。v-else-if不能脱离v-if单独出现。v-else不写值,且必须紧跟在v-if或v-else-if元素后面。v-if、v-else-if、v-else之间不要插入其它元素,否则分支链断裂。- 多分支判断顺序要从严格到宽松,避免先命中错误分支。
- 需要一次控制多个元素时,用
<template>分组,避免额外 DOM。
为什么需要 v-show:从 v-if 的“销毁重建”说起
上一节我们用的是 v-if。笔记里第一行注释已经把它的行为说得很清楚:v-if 为 true 表示渲染元素,为 false 表示不渲染元素。也就是说,条件一变,这个 <p> 在 DOM 树里就被整个创建或整个移除。
这在很多场景下是对的,但有些场景不合适。比如你只是想让一段内容暂时“藏起来”,一会儿还要原样拿出来,反复地销毁再重建,元素上的状态、事件、动画都要重新来一遍。v-show 就是为这种情况准备的:元素始终留在 DOM 里,只通过 CSS 的 display:none 把它藏起来。笔记注释里写得很明确:“v-show 为 true 表示显示元素,false 表示隐藏元素(通过 display:none)”。
所以第一层认知是:v-if 管“有没有”,v-show 管“看不看得见”。
怎么想:指令的值最终都要被求成一个布尔值
v-if 和 v-show 的值都是布尔值——这句也是笔记原话。但你在模板里写的并不一定是 true / false 这两个字面量,可能写的是一个变量名、一个字符串、一个数字。Vue 要做的事是:把双引号里的这段内容当成一个表达式求值,再把结果按 JavaScript 的真假规则转换一次。
这个“双引号里是一段表达式”的认识,在讲 v-bind 时已经反复强调过:双引号里的内容会被当作变量或者表达式去实例里找值;如果你想让它就是一段字符串,得在双引号里面再套一层单引号。这条规则同样适用于 v-show,下面的例子全部靠它来解释。
怎么做:把笔记里的十条语句一条条跑出来
先把笔记里的代码原样跑起来:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="root">
<!-- <p v-if="isShow">v-if</p>-->
<!-- <p v-show="isShow">v-show</p>-->
<!-- <p v-show='isShow'>1</p>-->
<!-- <p v-show=isShow>2</p>-->
<p v-show="'isShow'">3</p>
<p v-show="''">4</p>
<p v-show="1">5</p>
<p v-show="0">6</p>
<p v-show="true">7</p>
<p v-show="false">8</p>
<p v-show="null">9</p>
<p v-show="undefined">10</p>
</div>
</body>
<script>
// v-if与v-show的值都是布尔值。
// v-if为true表示渲染元素,false表示不渲染元素
// v-show为true表示显示元素,false表示隐藏元素(通过display:none)
// 注意1:空字符串,0,undefined,null作为判断条件相当于false
// 注意2:非空字符串,1作为判断条件相当于true
new Vue({
el:"#root",
data:{
// isShow:true
}
})
</script>
</html>运行后打开开发者工具(F12)看 Elements 面板,会看到第 3、5、7 条还在,第 4、6、8、9、10 条的 <p> 上多了一个内联样式 display: none。注意:它们都还在 DOM 里,只是看不见——这正是 v-show 和 v-if 最本质的区别。
逐条对照笔记里的两条“注意”:
- 第 3 条
v-show="'isShow'":双引号里面套了单引号,按前面讲v-bind时的结论,这表示它是一段字符串,而不是去实例里找isShow这个属性。非空字符串按真假规则算true,所以显示。 - 第 4 条
v-show="''":同样是字符串,但是空字符串。空字符串在 JavaScript 里算假,所以隐藏。 - 第 5、6 条:数字
1为真、0为假。这里不要和字符串"0"混淆,"0"是非空字符串,反而是真。 - 第 7、8 条:直接的布尔字面量
true/false,最没有歧义。 - 第 9、10 条:
null和undefined都算假。
再回头看被注释掉的那几行。如果把 data 里的 isShow: true 放开,同时放开第 2 行 v-show="isShow",元素会显示;把它改成 false,元素被 display:none 隐藏。而如果你没有在 data 里定义 isShow(也就是现在这份代码的状态),模板里读到的就是 undefined,结果仍然是隐藏——它不会报错,只是“找不到,当假处理”,这一点很容易让人误以为“指令写错了”。
第 1 行和第 3 行那两种引号写法(v-show='isShow'、v-show=isShow)之所以被注释掉,是因为它们和第 2 行语义完全一样:单引号、双引号、不加引号,在这里都只是包裹表达式的外壳,里面的 isShow 依旧是要去实例里找的属性名。真正改变语义的是在引号里面再加一层引号,把内容变成字符串。
易错点
第一,把“字符串”和“变量”搞混。v-show="isShow" 是去 data 里找 isShow;v-show="'isShow'" 是拿字符串 'isShow' 本身去判断,非空即真。判断结果一个是看数据,一个是恒为真,差别极大。
第二,以为 v-show 会把元素删掉。它只加 display:none,DOM 节点还在,所以如果你在控制台里数 document.querySelectorAll('p').length,数量不会因为 v-show 变少,只有 v-if 才会。
第三,把 0 和 "0"、null 和 "null" 当成一回事。前者是假,后者是非空字符串,是真。写的时候如果不确定,宁可直接写 true / false,或者把判断逻辑放在 data / computed 里算好,模板里只写变量名。
第四,data 里漏定义属性。v-show="isShow" 而 data 里没有 isShow 时页面不报错、只是隐藏,排查时容易一头雾水。养成习惯:模板里出现的变量名,先去 data 里确认它确实存在。
v-bind 的基本绑定:把“写死的属性”交给数据
为什么需要:普通标签里写 src="地址" 是静态的,数据一变视图不会跟着变;要让图片地址、宽度等由 Vue 实例数据控制,就要用 v-bind。怎么想:先把普通标签跑通,再把属性值换成实例属性,最后用指令绑定。怎么做:在 data 中准备 imagSrc、imagW,模板写 v-bind:src="imagSrc"、v-bind:width="imagW"。调试时先写 src="imagSrc",浏览器里看到的是字符串而不是图片地址,说明必须加指令。易错点:v-bind 后面不能漏冒号;属性值不加引号会被当成变量去实例里找,找不到就报错。
html
<img v-bind:src="imagSrc" v-bind:width="imagW">字符串、实例属性与动态属性名
为什么需要:同样写在引号里,有时要“字符串”,有时要“实例属性”,混淆就会渲染错。怎么想:v-bind:src="imagSrc" 引号内是实例属性;v-bind:src="'地址'" 引号内再套单引号才是字符串。动态属性名用中括号:v-bind:[w]="imagW",其中 w 是实例属性,它的值作为属性名。怎么做:data 里写 w: 'width',模板写 :[w]="imagW"。易错点:v-bind:[w] 不是把 w 当属性名,而是把 w 的值当属性名;v-bind 可简写为 :。
html
<img :src="imagSrc" :[w]="imagW">style 特殊:字符串、对象、数组
为什么需要:style 既要能写固定样式,也要能由数据控制。怎么想:普通写法 style="background:yellow" 可以;一旦写成 :style="background:yellow",Vue 会把 background:yellow 当变量解析,编译报错。怎么做:固定字符串用普通写法;绑定字符串要写成 :style="'background:yellow'";更常用对象 :style="{ background: 'yellow', color: 'red' }";数组写法中每个元素应是对象,字符串数组不生效。易错点:对象属性名是样式名,值才是样式值;数组里写字符串不会按预期生效。
html
<p :style="{ background: 'yellow', color: 'red' }">样式</p>class 特殊:对象布尔值控制类名
为什么需要:类名经常要根据状态切换,直接拼字符串麻烦。怎么想::class 的值可以是字符串、数组,最常用对象;对象属性名就是类名,值为 true 时生效,false 时无效。怎么做::class="{ bg: true, cl: false }",bg 类生效,cl 类不生效。易错点:对象里的 bg 是类名,不是实例属性;如果写成 :class="bg",bg 才会去实例里找。转写稿中强调这种布尔值写法“用得非常多”。
html
<p :class="{ bg: true, cl: false }">类名控制</p>v-on 基本用法:事件绑定的三种写法
为什么需要:数据改变视图会自动更新,但“点击按钮改数据”需要事件。怎么想:原生写 onclick,Vue 用 v-on:click,简写 @click。怎么做:第一种直接写语句 @click="name++";第二种写函数名 @click="changeName";第三种函数名加括号传参 @click="changeName2(3)",需要事件对象时传 $event。调试时在函数里输出 this,可看到指向 Vue 实例;输出 $event.target 可拿到按钮。易错点:函数名加括号不会立即调用,仍是点击时调用;不要把原生 event 和 Vue 的 $event 混用。
html
<button @click="name++">加一</button>
<button @click="changeName">调用函数</button>
<button @click="changeName2(3)">传参</button>v-model 双向绑定:视图变,数据也变
为什么需要::value="str" 只是单向绑定,输入框内容变了数据不变。怎么想:双向绑定 = 数据变视图变 + 视图变数据变。怎么做:手写可用 :value="str" 加 @input,在事件里把 $event.target.value 赋给 str;Vue 提供 v-model="str" 简化,它相当于 :value 和 @input 的组合。易错点:v-model 主要与表单元素结合,写到 div 上会报元素类型不对;手写事件时别把 $event 漏掉。
html
<input v-model="str">v-model 修饰符:trim、number、lazy
为什么需要:收集表单数据时常要去空格、保数字、减少更新次数。怎么想:修饰符写在指令后面。怎么做:.trim 去除左右空格,中间空格不去;.number 把输入值按数字处理,避免 1 + 2 变成字符串拼接;.lazy 让输入过程中不立即更新,失去焦点后再更新数据。易错点:.trim 不能去中间空格;.number 不是所有输入都强制成功;.lazy 不是不更新,而是延迟到失焦。
html
<input v-model.trim="str">
<input v-model.number="a">
<input v-model.lazy="str">课堂画面






代码示例
示例一:模板中的插值表达式 {{ }}(含多实例挂载)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<!-- 同一个页面,支持多个实例挂载至不同元素中 -->
<div id="one">
<h1>{{str}}</h1>
</div>
<div id="two">
<h1>{{str}}</h1>
</div>
<div id="root">
<!--将Vue实例下的属性num进行渲染-->
<p>{{num}}</p>
<!--将Vue实例下的属性str进行渲染-->
<p>{{str}}</p>
<!--将字符串进行渲染-->
<p>{{"str"}}</p>
<!--将数字进行渲染-->
<p>{{1}}</p>
<!--将Vue实例下的属性bol进行渲染-->
<p>{{bol}}</p>
<!--将布尔值进行渲染-->
<p>{{false}}</p>
<!--将Vue实例下的属性fn进行渲染-->
<p>{{fn}}</p>
<!--将Vue实例下的fn函数运行的结果进行渲染-->
<p>{{fn()}}</p>
<!--将Vue实例下的属性arr进行渲染-->
<p>{{arr}}</p>
<!--将Vue实例下的属性obj进行渲染-->
<p>{{obj}}</p>
<p>{{null}}</p>
<p>{{undefined}}</p>
<p>{{sex===1?"男":"女"}}</p>
<p>{{str.split("").reverse().join("")}}</p>
<template></template>
</div>
</body>
<script>
new Vue({
el:"#one",
data:{
str:"one"
}
})
new Vue({
el:"#two",
data:{
str:"two"
}
})
// 总结1:插值表达式支持字符串,数字,布尔,对象,数组,方法
// 总结2:undefined,null不会进行任何输出(与React相同)
// 总结3:数组不支持直接展开(React支持)
// 总结4:支持输出对象(React不支持)
// 总结5:Vue中包裹标签为<template></template>
new Vue({
el:"#root",
data:{
num:1,
str:"师法魔级超",
bol:true,
arr:[1,2,3,4],
sex:1,
obj:{
userName:"zhangsan"
}
},
methods:{
fn(){
return this.str.split("").reverse().join("");
}
}
})
</script>
</html>关键行 / 易错点
el:"#one"/el:"#two":一个页面可以new多个 Vue 实例,各挂各的元素,数据互不影响,这是本节 2-4 的核心。{{fn}}输出的是函数体源码,{{fn()}}输出的才是返回值,两者完全不同,别写混。{{null}}、{{undefined}}什么都不输出(不报错),与 React 一致。{{arr}}只能输出1,2,3,4这种形式,数组不支持直接展开;但{{obj}}可以直接输出对象(React 里不行)。- 插值里只能写表达式(三元、方法调用、链式调用都行),不能写
var a = 1这类语句。 - 插值只能写在标签内容区,写到属性上(如
src="{{imgSrc}}")无效 —— 属性渲染要用v-bind。
示例二:v-bind 属性渲染(含简写、动态属性名、style/class 特殊)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
<style>
.bg{ background: yellow; }
.color{ color: red; }
</style>
</head>
<body>
<div id="root">
<!-- 1. 普通写法:属性值写死 -->
<img src="把你自己复制到的图片地址粘在这里" width="50">
<a href="http://www.baidu.com">百度</a>
<!-- 2. 错误示范:插值不能用在属性上 -->
<img src="{{imgSrc}}">
<!-- 3. 正确写法:v-bind 绑定实例属性 -->
<img v-bind:src="imgSrc" v-bind:width="imgW">
<!-- 4. 简写:冒号就相当于 v-bind: -->
<img :src="imgSrc" :width="imgW">
<a :href="siteHref">百度</a>
<!-- 5. 绑定的就是一个字符串:外层双引号里再套单引号 -->
<a :href="'http://www.baidu.com'">百度</a>
<a :href='"http://www.baidu.com"'>百度</a>
<!-- 6. 动态属性名:中括号里的 w 是实例属性,值为 'width' -->
<img :src="imgSrc" :[w]="imgW" :[h]="imgH">
<!-- 7. style 特殊:值可以是对象 -->
<p :style="{background:'yellow', color:'red'}">style 对象</p>
<p :style="styleObj">style 绑定实例对象</p>
<!-- 8. class 特殊:对象的属性名是类名,值是布尔值 -->
<p :class="{bg: isBg, color: isColor}">class 对象</p>
</div>
</body>
<script>
new Vue({
el:"#root",
data:{
imgSrc:"把你自己复制到的图片地址粘在这里",
imgW:100,
imgH:400,
siteHref:"http://www.baidu.com",
w:"width",
h:"height",
styleObj:{ background:"yellow", color:"red" },
isBg:true,
isColor:false
}
})
</script>
</html>关键行 / 易错点
src="{{imgSrc}}"无效,属性渲染必须用v-bind,这是本节第一个坑。v-bind:src="imgSrc":双引号里的内容被当成表达式,imgSrc是实例属性名;写成:src就是它的简写,工作里基本都用简写。- 想绑字符串要再加一层引号:
:href="'http://www.baidu.com'"(外双内单)或:href='"..."'(外单内双);直接写:href="http://www.baidu.com"会被当表达式解析而报错。 :[w]="imgW"是动态属性名,w的值是"width",最终等价于:width="imgW";不加中括号:w就是绑到w这个属性上。- 数字别乱加引号:
:width="100"是数字100,:width="'100'"是字符串'100'。 :style的值可以是对象,外层双引号、里层单引号,别写成:style="{background:"yellow"}"。:class="{bg: isBg}"中 属性名就是类名,值是布尔值:true该类生效,false无效;这里bg是对象属性名,不是 data 里的变量,所以不用加引号也不会报错。style/class也可以绑定实例上的对象(styleObj),响应式数据一变样式就跟着变。
示例三:v-model 双向绑定与修饰符
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 1. 单向绑定:数据变视图变,视图变数据不变 -->
<input type="text" :value="str">
<h3>{{str}}</h3>
<!-- 2. 双向绑定:v-model -->
<input type="text" v-model="str">
<!-- 3. v-model 的本质::value + @input -->
<input type="text" :value="str" @input="changeStr">
<input type="text" :value="str" @input="str = $event.target.value">
<!-- 4. 修饰符 .trim 去除左右空格 -->
<input type="text" v-model.trim="str2">
<h3>[{{str2}}]</h3>
<!-- 5. 修饰符 .number 保持数字类型 -->
<input type="text" v-model.number="a"> +
<input type="text" v-model.number="b"> =
<input type="text" :value="sum">
<button @click="add">=</button>
<!-- 6. 修饰符 .lazy 失去焦点后才更新 -->
<input type="text" v-model.lazy="str3">
<h3>{{str3}}</h3>
</div>
</body>
<script>
new Vue({
el:"#root",
data:{
str:"刘德华",
str2:"",
str3:"",
a:1,
b:2,
sum:0
},
methods:{
changeStr(e){
this.str = e.target.value;
},
add(){
this.sum = this.a + this.b;
}
}
})
</script>
</html>关键行 / 易错点
:value="str"只是把数据塞进value属性,改输入框数据不会变,这叫单向绑定。v-model="str"是双向绑定,本质就是:value+@input的语法糖(示例第 3 段是它的展开写法),所以两种写法效果一样。@input="str = $event.target.value"是行内语句写法,$event就是事件对象,target.value才是输入框的值。v-model只能用在表单元素(input/textarea/select)上,写在div上会报错。.trim只去左右空格,中间空格去不掉;提交表单前一般都要加。- 不加
.number时v-model拿到的是字符串,1 + 2会变成"12"(字符串拼接);加.number才是3。 .lazy是失去焦点后才把数据同步过去,避免每敲一个字符就更新一次视图。- 方法名别改成
alert这种原生方法名,Vue 会去实例的 data/methods 里找,找不到就报“属性或方法未定义”。
常见错误
1. 两个实例挂到同一个根元素,或挂到 body 上
- 现象:只有第一块内容渲染出来,第二块要么空白,要么整页被替换成
{{str}}原文。 - 原因:一个
el只能被一个实例接管;后挂载的会覆盖先挂载的编译结果。挂到body/html则等于让 Vue 接管整个页面。 - 修法:每个实例给一个独立的根
div,el指向互不嵌套、互不重叠的元素。
2. 插值里写语句而不是表达式
- 现象:
{{ if (a) ... }}、{{ let x = 1 }}、{{ num++ }}直接报编译错误或页面渲染中断。 - 原因:
{{ }}只接受单个表达式,会求值后转成字符串输出;语句没有返回值。 - 修法:把逻辑挪到
methods/computed,模板里只留{{ getAgeStr() }}这种一次调用。
3. v-text / v-html 的值加了反引号或又套了插值
- 现象:页面上直接显示
userName这个单词,或者{{str}}原样露出来。 - 原因:反引号在模板里是模板字符串字面量,指令拿到的是字符串
"userName"而不是属性名;而指令所在标签内的插值本来就会被覆盖,写了也白写。 - 修法:
v-text="userName",要字面量就v-text="'userName'";标签内部不要再塞{{ }}。
4. v-bind 引号套错层:想要变量却写成了字符串
- 现象:图片裂开(
src="imgSrc"),控制台Property or method "imgSrc" is not defined。 - 原因:双引号内是 JS 表达式域,
"imgSrc"里再加引号就变成字符串字面量;反过来:src=imgSrc省引号才是取变量。 - 修法:变量不加内层引号,字符串才加——
:src="imgSrc"/:src="'imgSrc'"。同理:[attrName]取变量名,:attrName才是固定属性。
5. v-if / v-else-if / v-else 之间夹了别的元素
- 现象:
v-else分支永远不出现,控制台警告v-else used on element without corresponding v-if。 - 原因:这三个指令靠相邻兄弟节点配对,中间插入任何标签(哪怕注释外的空元素)都会断链。
- 修法:把三者挪成紧邻兄弟;需要包一层时用
<template>,不要用<div>。
6. v-show 写了带引号的字符串,永远显示
- 现象:
v-show="'isShow'"、v-show="0"之类,明明想隐藏却一直显示(或反之)。 - 原因:判断走的是 JS 真值规则,非空字符串恒为真;
0/''/null/undefined为假。 - 修法:只写变量名
v-show="isShow",值必须是布尔或能转布尔的表达式。
7. :style 直接写 CSS 声明串
- 现象:
:style="background: yellow"报Invalid expression。 - 原因:
:之后是表达式域,background: yellow不是合法 JS。 - 修法:要么去掉冒号当普通属性写
style="background:yellow",要么写成对象:style="{background:'yellow'}"。
8. v-model 用在非表单元素上,或忘了 .number
- 现象:①
<div v-model="str">报元素类型错误;② 加法器里1 + 2得到"12"。 - 原因:
v-model本质是:value+@input的语法糖,只对表单控件/组件成立;输入框取回的value恒为字符串。 - 修法:① 换成
input/textarea/select;② 加修饰符v-model.number,需要去空格加.trim,需要失焦再同步加.lazy。
练习
练习 1|多实例隔离 页面放两个 div:左边一个计数器(按钮点击 num++),右边一个输入框 + 实时回显。要求两个实例的 data 完全独立,并验证:点左边按钮时右边输入框内容不受影响,反之亦然。
练习 2|插值表达式能力盘点 在一个实例的 data 里准备 num / str / bol / arr / obj / sex / empty:null,用插值分别输出它们;再额外输出:字符串反转结果、sex===1?"男":"女"、arr.length。观察 null 和 undefined 在页面上留下什么,并解释为什么数组直接输出不会像 React 那样自动展开。
练习 3|v-bind 四种写法对照 用 data 提供 imgSrc、imgW、attrName(值为 "title"),完成: ① <img :src="imgSrc" :width="imgW">; ② 一个 <a>,href 分别用「变量」「单引号字符串」两种写法各写一次; ③ 用 :[attrName]="'hello'" 动态生成属性名; ④ 全部改写成省略 v-bind 的简写形式。写完后在 Elements 面板核对每个属性最终值。
练习 4|条件渲染与显示切换 用 v-if / v-else-if / v-else 实现年龄分级(>80 老年 / >40 中年 / >18 青年 / 其余婴儿),再加一个按钮在「登录态」和「未登录态」间切换(未登录时渲染两个输入框)。最后把同一段内容分别用 v-show 写一份,打开 Elements 面板对比两者的 DOM 差异,并说出什么场景该用哪个。
练习 5|样式与表单综合 做一个「夜间模式」小面板:用 :class="{ dark: isDark }" 控制类名,用 :style="{ background: bgColor, color: textColor }" 控制内联样式;面板内放两个输入框做加法器(结果用 v-model.number 保证是数字相加),再加一个用户名输入框用 v-model.trim 收集,并用一个 <h3> 回显(换成 v-model.lazy 再观察回显时机变化)。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | v-bind | 30:09 |
| 2 | style特殊 | 08:52 |
| 3 | class特殊 | 07:21 |
| 4 | v-on的基本用法 | 10:55 |
| 5 | v-model | 12:05 |
| 6 | v-model修饰符 | 11:03 |
小结
- 指令的引号就是表达式边界:双引号里是 JS 表达式域,写变量不加引号、写字面量加内层引号。这一条同时适用于
v-bind、v-on、v-if、v-show,记住它比记十个指令都值钱。 - 模板只做「取值 + 一次调用」:凡是需要 if/循环/多步运算的,一律搬到
methods或computed。模板里出现语句,就是设计出问题的信号。 {{ }}会被指令覆盖:标签上写了v-text/v-html,内部插值不生效;调试时看到「插值原样输出」,先怀疑指令值被当成了字符串。v-if管「有没有」,v-show管「显不显示」:前者有编译期开销、切换成本高;后者只改display。频繁切换用v-show,条件稳定但初始不该渲染的用v-if。v-if家族靠相邻关系配对,一旦中间插了元素就断链——这是最容易被「顺手加个 div」破坏的地方,包装一律用<template>。v-bind的两种「动态」要分清:动态值(:src="x")和动态属性名(:[key]="x")。后者在做通用组件、透传属性时是刚需。class的对象语法是工程里最高频的写法:{ 类名: 布尔 },属性名就是最终类名、值就是开关;而style优先用对象语法,字符串拼接只在静态场景用。v-model不是魔法,它就是:value+@input的语法糖;一旦遇到类型、空格、性能问题,答案都在修饰符上:.number/.trim/.lazy。- 调试固定顺序:先看控制台报错 → 再看 Elements 里 DOM 到底有没有 → 最后看 Vue Devtools 里数据变没变。三步走完,绝大多数「不生效」都能自己定位。