主题
Vue 快速上手与指令(上)(三)
本页对应 Vue 快速上手与指令(上),共 6 段视频,约 53 分钟。
学习目标
- 能够使用
v-bind绑定图片src,结合点击事件与下标变量实现上一张/下一张切换,并在首尾图片时通过条件指令控制按钮显示或隐藏。 - 能够使用
v-for遍历数组渲染列表,正确获取每一项与下标,并在不需要下标时省略下标参数。 - 能够为
v-for添加:key,说明其唯一性要求,并在有id时优先使用id而非index。 - 能够使用
v-on绑定点击事件并传递参数,通过filter过滤数组实现小黑的书架删除功能,理解数据驱动视图。 - 能够使用
v-model对表单元素进行双向绑定,实现表单值获取、数据回填与重置清空。
知识点
事件绑定指令先解决什么问题
为什么需要:原生写页面时,要给按钮加点击事件,往往要 querySelector 找到 DOM,再 addEventListener 或写 onclick;事件里还要手动改 DOM。Vue 的思路是:事件触发后只改数据,视图跟着变,减少 DOM 操作。
怎么想:先找“谁触发事件”,再决定“触发后执行一行表达式,还是调用 methods 里的函数”。事件名不加 on,如 click、input。
怎么做:用 v-on:事件名,简写为 @。本节笔记给出三种写法:
<button v-on:事件名="内联语句">按钮</button><button v-on:事件名="处理函数">按钮</button><button v-on:事件名="处理函数(实参)">按钮</button>v-on:简写为 @
易错点:@click 不要写成 @onclick;内联语句能直接访问 data;调用函数时,加不加括号含义不同。
内联语句:一行能说清就直接写
为什么需要:像加减、取反这种简单逻辑,不必单独写函数。
怎么想:事件触发后只做一件事,且能写成表达式,就写内联语句。
怎么做:笔记代码如下,count--、count++ 直接修改 data,页面自动更新。
js
<div id="app">
<button @click="count--">-</button>
<span>{{ count }}</span>
<button v-on:click="count++">+</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
count: 100
}
})
</script>易错点:内联语句里不要写复杂多行逻辑;复杂逻辑放 methods,便于调试和复用。
事件处理函数:复杂逻辑放 methods
为什么需要:切换显示隐藏、删除数据、提交表单等逻辑不止一行,写在模板里会乱。
怎么想:事件只负责“调用方法”,方法里通过 this 改数据。转写稿中做图片切换时,先准备数组存图片地址,再用 index 表示当前下标;点“下一页”让 index++,点“上一页”让 index--,本质就是改数据。
怎么做:methods 与 data 同级,方法内 this 指向 Vue 实例。笔记示例原代码中按钮还没绑定事件,补全后如下:
js
<div id="app">
<button>切换显示隐藏</button>
<h1 v-show="isShow">黑马程序员</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
isShow: true
}
})
</script>补全绑定:
html
<button @click="toggle">切换显示隐藏</button>js
methods: {
toggle() {
this.isShow = !this.isShow
}
}图片切换可写成:
html
<img :src="list[index]" alt="当前图片">
<button @click="index--" v-show="index > 0">上一页</button>
<button @click="index++" v-show="index < list.length - 1">下一页</button>易错点:methods 不要写进 data;方法里改数据要加 this;v-show 的条件要按临界值判断,第一张不显示“上一页”,最后一张不显示“下一页”。
传参和事件对象:删除时要告诉方法“删谁”
为什么需要:列表里每行都有删除按钮,不传 ID,方法不知道删哪条。转写稿中小黑书架删除功能就是先 @click 调方法,再传 item.id。
怎么想:先确认方法需不需要参数。不需要参数,写方法名即可,方法内可用 e 当事件对象;需要参数,就在模板里加括号传实参,若还要事件对象,用固定写法 $event。
怎么做:笔记售货机代码如下:
js
<style>
.box {
border: 3px solid #000000;
border-radius: 10px;
padding: 20px;
margin: 20px;
width: 200px;
}
h3 {
margin: 10px 0 20px 0;
}
p {
margin: 20px;
}
</style>
<div id="app">
<div class="box">
<h3>小黑自动售货机</h3>
<button>可乐5元</button>
<button>咖啡10元</button>
<button>牛奶8元</button>
</div>
<p>银行卡余额:{{ money }}元</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
money: 100
}
})
</script>补全按钮和方法:
html
<button @click="buy('可乐', 5)">可乐5元</button>
<button @click="buy('咖啡', 10)">咖啡10元</button>
<button @click="buy('牛奶', 8)">牛奶8元</button>js
methods: {
buy(name, price) {
this.money -= price
}
}书架删除可写:
html
<button @click="del(item.id)">删除</button>js
methods: {
del(id) {
this.bookList = this.bookList.filter(item => item.id !== id)
}
}易错点:传了参数却忘记加括号;需要事件对象时写成 $event,不是 event;删除不要手动操作 DOM,转写稿里强调“数据驱动视图”,应改数组数据,filter 会返回新数组再赋值回去。调试时可先 console.log(id) 确认传参,再写筛选逻辑。
小结:事件绑定的判断顺序
先看事件触发后要做什么:一行表达式能完成,用内联语句;逻辑复杂,放 methods;需要区分列表项,传 ID 或下标;需要事件对象,无参时用 e,有参时用 $event。最后记住:事件里改数据,视图由 Vue 更新。
为什么需要属性绑定
普通 HTML 里写 <img src="./imgs/10-02.png">,地址是写死的;一旦数据变化,页面不会自动跟着变。Vue 里我们希望“数据变,标签属性也变”,比如图片地址、提示文字 title、链接 url 等都由 data 决定,这时就需要属性绑定指令 v-bind。
怎么想:把标签属性的值交给 Vue 表达式,而不是写死字符串。怎么做:用 v-bind:属性名="表达式"。易错点:v-bind 后面是冒号加属性名,不是普通 HTML 属性;表达式里写变量名即可,不要再套 {{ }}。
语法与简写:从 v-bind:src 到 :src
本节笔记给出的语法是:v-bind:属性名="表达式",并且 v-bind: 可以简写为 :。例如图片的 src 存在 data 中,可以写 <img v-bind:src="url" />,也可以写 <img :src="url" />。笔记中的示例代码保留如下:
js
<div id="app">
<img v-bind:src="imgUrl" v-bind:title="msg" alt="">
<img :src="imgUrl" :title="msg" alt="">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
imgUrl: './imgs/10-02.png',
msg: 'hello 波仔'
}
})
</script>为什么需要简写:属性多时写全 v-bind: 很啰嗦。怎么想:看到 :src、:title 就等价于 v-bind:src、v-bind:title。怎么做:保留冒号,后面直接写属性名。易错点:简写是 :src,不是 :bind;笔记里“简写成 => :bind”应理解为“用冒号简写”,实际写法是 :属性名。另外 v-bind:title="msg" 中 msg 是变量,不要写成字符串 "msg",否则显示的是字面量。
调试过程:切换图片案例怎样落到属性绑定
转写稿里的“切换图片”案例,核心就是换 src 属性。先准备一个图片地址数组,例如 list 里放多张图片地址;默认展示第 0 张时,写成 :src="list[0]",刷新页面能看到第一张。把 0 改成 1,刷新后变成第二张,这说明“下标可变,图片就变”。于是再准备一个变量 index 表示下标,默认 index: 0,模板写成 :src="list[index]"。点“下一页”让 index++,点“上一页”让 index--,图片就会切换。这里不是手动改 DOM,而是改数据,让属性绑定自动更新。
调试时要注意边界:index 为 0 时已经是第一张,上一页按钮不应再显示;index 为最后一张下标时,下一页按钮不应再显示。转写稿中用 v-show 或 v-if 加条件控制,上一页可判断 index > 0,下一页可判断 index < list.length - 1。为什么用 length - 1:数组下标从 0 开始,最后一张下标是长度减一。易错点:index 初始值要写 0;不要写成负下标;条件不要写死成固定数字,否则图片数量变化就不准;控制按钮显示隐藏时仍要保证点击逻辑只改 index。
常见易错点与检查清单
第一,v-bind 绑定的是属性值,不是标签内容;标签内容用插值。第二,v-bind:src="imgUrl" 中 imgUrl 是表达式,若写 v-bind:src="{{ imgUrl }}" 就是错误思路。第三,简写冒号不能丢::src="url" 正确,src="url" 只会把字符串 url 当普通地址。第四,绑定 title、url 等属性同理,数据变页面变。第五,调试时先看 Vue 是否挂载成功、data 中变量名是否与模板一致,再刷新看属性是否更新;若图片不显示,检查路径和变量值,不要先怀疑指令。掌握这些后,切换图片、动态提示文字等场景都可以用属性绑定完成。
需求与核心思路
这个案例要做的效果是:页面默认展示数组中的第一张图片,点击“上一页”“下一页”按钮,可以来回切换数组中的图片;当已经是第一张时,“上一页”按钮隐藏;当已经是最后一张时,“下一页”按钮隐藏。
为什么需要这样想?
切换图片本质上就是换 <img> 的 src 属性。转写稿里说得很直接:“切换图片呢,其实就是换它的 src 属性。”但如果我们手动写很多张 <img>,再控制谁显示谁隐藏,代码会很乱。更合理的做法是:把图片路径集中放到数组里,再用一个“下标变量”决定当前显示哪一张。这就是 Vue 里“数据驱动视图”的思路。
怎么想?
准备数组 list 存图片路径;准备 index 表示当前下标,默认是 0;图片的 src 绑定 list[index];点“下一页”让 index++,点“上一页”让 index--;最后用 v-show 控制按钮是否显示。
怎么做?
先保留笔记中的原始结构:
html
<div id="app">
<button>上一页</button>
<div>
<img src alt="">
</div>
<button>下一页</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
'./imgs/11-00.gif',
'./imgs/11-01.gif',
'./imgs/11-02.gif',
'./imgs/11-03.gif',
'./imgs/11-04.png',
'./imgs/11-05.png',
]
}
})
</script>易错点:
数组名、图片路径要写对;后面绑定 src 时不要忘记冒号,src="list[index]" 只是普通字符串, :src="list[index]" 才是 Vue 表达式。
让图片跟着下标走
为什么需要下标?
数组里有很多张图片,页面同一时间只显示一张。必须有一个变量告诉 Vue:“现在取第几张”。转写稿里反复强调:“再来一个变量表示下标,index 默认让它显示 0,为什么?第 0 张嘛。”
怎么想?list 是图片仓库,index 是取货编号。编号变了,取出来的图片就变了,所以不需要操作 DOM,只改数据即可。
怎么做?
在 data 中加 index: 0,图片写成:
html
<img :src="list[index]" alt="波仔学习之旅图片">易错点:index 不要写成字符串 '0';src 前必须加 :;如果写成 list[0],图片就永远固定在第一张,失去切换能力。
按钮改下标
为什么需要按钮事件?
只把 index 写死没有意义,用户要通过“上一页”“下一页”改变它。转写稿的调试过程是:先在控制台手动把 index 加一、减一,看图片是否变化;确认思路后,再把加一减一放到按钮事件里。
怎么想?
“下一页”就是让当前下标加一,“上一页”就是让当前下标减一。注意方法名要和模板里调用的一致。
怎么做?
模板补上点击事件:
html
<button @click="prev" v-show="index > 0">上一页</button>
<div>
<img :src="list[index]" alt="波仔学习之旅图片">
</div>
<button @click="next" v-show="index < list.length - 1">下一页</button>方法中写:
js
methods: {
prev() {
this.index--
},
next() {
this.index++
}
}易错点:
方法里要写 this.index,不能直接写 index;模板里 @click="prev" 不要漏掉方法名;不要用 DOM 的 src 赋值去替代数据修改。
临界条件控制按钮
为什么需要控制按钮?
如果第一张还显示“上一页”,点一下 index 会变成 -1,取不到图片;最后一张还显示“下一页”,继续加会越界。转写稿说:“已经是第一张了,上一页按钮就别让它显示了;最后一张,下一页就不能显示了。”
怎么想?
“上一页”只在 index > 0 时显示;“下一页”只在 index < list.length - 1 时显示。不要写死 < 5,因为数组长度可能变化,转写稿也提醒:“如果图片不是这个数量,这个小数字就不对了,应该用 list.length - 1。”
怎么做?
用 v-show 或 v-if 都可以,本案例用 v-show 控制显示隐藏。上面模板已经写好。
易错点:
上一页条件不要写成 index >= 0,否则第一张仍显示;下一页条件不要写成 index <= list.length - 1,否则最后一张仍显示;隐藏后按钮点不了,正好避免越界。
调试与完整检查
调试时可以先刷新页面,确认默认显示 list[0];再在控制台把 index 改成 1、2,看图片是否跟着变;然后点按钮逐张切换,观察第一张和最后一张按钮是否隐藏。转写稿中就是先手动改下标验证,再把逻辑接到按钮上。
完整关键代码如下:
html
<div id="app">
<button @click="prev" v-show="index > 0">上一页</button>
<div>
<img :src="list[index]" alt="波仔学习之旅图片">
</div>
<button @click="next" v-show="index < list.length - 1">下一页</button>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
index: 0,
list: [
'./imgs/11-00.gif',
'./imgs/11-01.gif',
'./imgs/11-02.gif',
'./imgs/11-03.gif',
'./imgs/11-04.png',
'./imgs/11-05.png',
]
},
methods: {
prev() {
this.index--
},
next() {
this.index++
}
}
})
</script>最后记住:这个案例不是“操作图片元素”,而是“改下标”;下标一变,src 绑定结果就变,页面自然切换。
从“切换图片”理解下标驱动
为什么需要:在讲 v-for 之前,先要理解“列表里的数据可以被下标取出来”。转写稿里的图片切换案例,本质不是直接操作 DOM 换 src,而是准备一个图片地址数组,再用一个下标变量决定当前显示哪一张。
怎么想:把多张图片地址放进 list,默认 index = 0;页面上的图片地址写成 list[index]。点“下一页”让 index++,点“上一页”让 index--,图片自然切换。按钮是否显示也要看临界条件:第一张时不显示“上一页”,最后一张时不显示“下一页”。
怎么做:
html
<img :src="list[index]" alt="当前图片">
<button v-show="index > 0" @click="index--">上一页</button>
<button v-show="index < list.length - 1" @click="index++">下一页</button>易错点:src 是属性绑定,必须写 :src,不能写死 src="list[index]";判断最后一张不要写死数字,应该用 list.length - 1,否则图片数量变化后按钮条件就错了。
v-for 的基本语法
为什么需要:页面经常要把数组里的每一项都渲染出来,比如水果列表、图书列表。手动写多个标签既重复又难维护,所以 Vue 提供 v-for 列表渲染指令。
怎么想:v-for 写“谁”身上,就循环“谁”。要循环 li,就把 v-for 加在 li 上;语法是 (item, index) in arr,其中 item 是数组每一项,index 是索引,不需要时可以省略。
怎么做:笔记中的核心语法如下:
js
//遍历对象
<div v-for="(value, key, index) in object">{{value}}</div>
value:对象中的值
key:对象中的键
index:遍历索引从0开始
//遍历数字
<p v-for="item in 10">{{item}}</p>
item从1 开始数组场景常写成:
html
<ul>
<li v-for="(item, index) in list" :key="index">
{{ index }} - {{ item }}
</li>
</ul>易错点:in 前后要有空格;item、index 是形参,名字可以换,但顺序不能乱;索引不用时可以写成 item in list,但不要漏掉空格。
书架案例:渲染与删除
为什么需要:只讲语法不够,实际项目里列表通常还要“渲染数据”和“删除某一项”。转写稿用“小黑的书架”演示了这个过程。
怎么想:渲染时用 v-for="item in bookList" 循环 li,书名写 {{ item.name }},作者写 {{ item.author }}。删除时不要直接操作 DOM,而是“数据驱动视图”:拿到被点项的 id,用 filter 过滤掉它,再重新赋值给 bookList。
怎么做:
html
<li v-for="item in bookList" :key="item.id">
<span>{{ item.name }}</span>
<span>{{ item.author }}</span>
<button @click="del(item.id)">删除</button>
</li>js
methods: {
del(id) {
this.bookList = this.bookList.filter(item => item.id !== id)
}
}易错点:filter 要保留“不等于当前 id”的项,条件写反就会删错;方法里要用 this.bookList;模板里传参要写 del(item.id),不能只写 del。
v-for 中的 key
为什么需要:Vue 在更新列表时希望尽量复用已有元素,提高性能。但删除列表项时,如果没有唯一标识,Vue 可能不知道你删的是哪一项,出现“删第一条却像删了最后一条”的复用问题。
怎么想:给每个循环项加一个唯一 key,相当于给元素编号。key 的值只能是字符串或数字,且不能重复。有 id 优先用 id,不要优先用 index,因为 index 会随着删除、排序变化。
怎么做:
html
<li v-for="item in bookList" :key="item.id">
{{ item.name }}
</li>易错点:key 要加冒号写成 :key,否则 item.id 会被当成普通字符串;key 不能重复;不要用会变化的 index 作为稳定标识。
本节易错点汇总
v-for加在要循环的标签上,不要加在父级却以为会循环子级。(item, index) in arr中item在前、index在后。- 遍历对象是
(value, key, index),遍历数字时item从 1 开始。 - 删除列表项要改数据,不要直接删 DOM。
- 循环列表必须重视
:key,有id用id,没有id再找不重复值。
需求拆解:先渲染,再删除
这个案例叫“小黑的书架”,目标只有两个:第一,根据左侧准备好的 booksList 数据,把右侧图书列表渲染出来;第二,点击某一行的“删除”按钮时,只删除当前这一行。页面效果如下:

这里要先建立一个思想:数据驱动视图。列表不是手写死的,而是由数组循环出来的;删除也不是去页面里找 li 再删 DOM,而是把数组里对应的那条数据去掉,页面自然跟着变。转写稿里反复强调“不要操作 DOM,改数据就行”,这是本案例的核心。
准备代码
先把笔记里的准备代码保留下来:
js
<div id="app">
<h3>小黑的书架</h3>
<ul>
<li>
<span>《红楼梦》</span>
<span>曹雪芹</span>
<button>删除</button>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
booksList: [
{ id: 1, name: '《红楼梦》', author: '曹雪芹' },
{ id: 2, name: '《西游记》', author: '吴承恩' },
{ id: 3, name: '《水浒传》', author: '施耐庵' },
{ id: 4, name: '《三国演义》', author: '罗贯中' }
]
}
})
</script>知识点一:用 v-for 渲染书架列表
为什么需要: 数据里有四条图书,如果手写四个 li,数据一多就要复制很多遍,而且以后数据变化时页面很难维护。所以需要用 v-for 循环数组,把每条数据渲染成一个 li。
怎么想: booksList 是数组,数组里每一项都是一个对象,例如 { id: 1, name: '《红楼梦》', author: '曹雪芹' }。循环谁,就给谁加 v-for。这里要循环 li,所以 v-for 写在 li 上。循环出来的当前项可以叫 item,然后通过 item.name、item.author 取书名和作者。
怎么做: 把原来的 li 改成循环写法,插值展示数据:
html
<li v-for="item in booksList" :key="item.id">
<span>{{ item.name }}</span>
<span>{{ item.author }}</span>
<button>删除</button>
</li>易错点: v-for 要写在真正需要重复的标签上;item 表示当前这一项,不要写成 booksList.name;in 前后要有空格;插值要用 {{ }}。转写稿里说的“循环遍历这个 li”,纠正过来就是:给 li 加 v-for,循环 booksList。
知识点二:点击删除,用 filter 过滤当前行
为什么需要: 页面上有多个删除按钮,如果只写一个普通点击事件,程序不知道你点的是哪一行。所以点击按钮时,必须把当前行的 id 传出去,告诉删除方法“我要删哪一条”。
怎么想: 删除的本质不是删 DOM,而是改 booksList。如果点击的是 id 为 2 的《西游记》,那就让新数组只保留 id !== 2 的项。filter 的作用就是筛选:返回 true 的留下,返回 false 的过滤掉。因此条件应写成 item.id !== id,意思是“把不等于当前点击 id 的图书留下来”,言外之意就是把当前这条删掉。
怎么做: 按钮上加 @click,调用方法并传入 item.id;在 methods 中写 del,用 filter 过滤后重新赋值给 this.booksList:
html
<button @click="del(item.id)">删除</button>js
methods: {
del(id) {
this.booksList = this.booksList.filter(item => item.id !== id)
}
}调试时可以按转写稿的思路:先在 del 里打印 id,点最后一个按钮看到 4,点第三个看到 3,说明传参正确;再写 filter,观察列表是否只少了当前行。
易错点: @click="del(item.id)" 不要漏掉括号和参数;methods 里的方法名要和模板中一致;filter 返回的是新数组,必须重新赋值给 this.booksList;条件不要写反,写 !== 才是留下其他项;方法里不要漏 this。这样,渲染和删除两个功能就完成了。
课件图示








代码示例
代码一:事件绑定 + 属性绑定 —— 图片切换案例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>波仔的学习之旅</title>
<style>
#app { text-align: center; }
#app img { width: 300px; height: 200px; margin: 10px 0; }
</style>
</head>
<body>
<div id="app">
<!-- v-show 控制上一页按钮:下标为 0(第一张)时隐藏 -->
<button v-show="index > 0" @click="index--">上一页</button>
<div>
<!-- v-bind:src 简写为 :src,值是一个表达式 list[index] -->
<img :src="list[index]" alt="">
</div>
<!-- 下标为最后一张时隐藏下一页 -->
<button v-show="index < list.length - 1" @click="index++">下一页</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
index: 0, // 当前展示的图片下标
list: [ // 图片路径数组(按实际素材路径替换)
'./imgs/11-00.gif',
'./imgs/11-01.gif',
'./imgs/11-02.gif',
'./imgs/11-03.gif',
'./imgs/11-04.png',
'./imgs/11-05.png'
]
}
})
</script>
</body>
</html>关键行与易错点
:src="list[index]":v-bind:后面接的是表达式而不是字符串,所以list[index]不能加引号写成list[0]那种"写死"的形式,否则切换下标图片不会变。@click="index++"/@click="index--":这是内联语句写法,直接写简单表达式即可,不必非要去 methods 里写函数。- 按钮显隐用
v-show(或v-if都行):条件是index > 0和index < list.length - 1。易错点是下一页写成index < 5这种硬编码数字,图片数量一变就错,应该用list.length - 1。 @是v-on:的简写,:src是v-bind:src的简写,初学阶段两种写法都要认识。- 数据驱动视图:整个过程没有操作 DOM,只是改
index这个数据,图片和按钮状态就自动更新了。
代码二:列表渲染 + 删除 —— 小黑的书架
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>小黑的书架</title>
<style>
li { margin: 8px 0; }
li span { display: inline-block; width: 120px; }
</style>
</head>
<body>
<div id="app">
<h3>小黑的书架</h3>
<ul>
<!-- v-for 循环 li,:key 用唯一不重复的 id -->
<li v-for="item in booksList" :key="item.id">
<span>{{ item.name }}</span>
<span>{{ item.author }}</span>
<!-- 调用处理函数并传参:把当前行的 id 传过去 -->
<button @click="del(item.id)">删除</button>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
booksList: [
{ id: 1, name: '《红楼梦》', author: '曹雪芹' },
{ id: 2, name: '《西游记》', author: '吴承恩' },
{ id: 3, name: '《水浒传》', author: '施耐庵' },
{ id: 4, name: '《三国演义》', author: '罗贯中' }
]
},
methods: {
del(id) {
// 用 filter 过滤掉当前 id,把结果重新赋给 booksList
this.booksList = this.booksList.filter(item => item.id !== id)
}
}
})
</script>
</body>
</html>关键行与易错点
v-for="item in booksList":item是数组中的每一项(这里是对象),所以取值要写item.name、item.author。下标用不到时小括号和 index 都可以省略,但in前后必须有空格。:key="item.id":key的值要加冒号,表示取变量;不加冒号就是字符串"item.id",四行 key 全一样会报错。key 必须唯一且推荐用 id,不推荐用 index(index 会随删除变化)。@click="del(item.id)":传参时必须写小括号,把当前行的 id 作为实参传入;methods 里的形参id接收它。this.booksList = ...:methods 中的this指向 Vue 实例,所以能取到 data 里的数据;漏写 this 就会报错。- 删除不要用 DOM 操作(如
removeChild),而是"改数据"——数组少一条,页面自动少一行,这就是数据驱动视图。 - 如果不传参而想拿事件对象,方法上不要加小括号,方法内直接用
e;如果既传参又要事件对象,实参写固定用法$event。
代码三:双向绑定 —— v-model 登录表单
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>v-model 双向绑定</title>
<style>
#app { padding: 20px; }
#app div { margin: 10px 0; }
#app input { margin-left: 10px; }
</style>
</head>
<body>
<div id="app">
<div>账号:<input type="text" v-model="username"></div>
<div>密码:<input type="password" v-model="password"></div>
<button @click="login">登录</button>
<button @click="reset">重置</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '', // 与账号输入框双向绑定
password: '' // 与密码输入框双向绑定
},
methods: {
login() {
// 数据就是输入框里的值,直接读数据即可
console.log('账号:', this.username, '密码:', this.password)
},
reset() {
// 改数据 = 清空输入框(数据回填同理)
this.username = ''
this.password = ''
}
}
})
</script>
</body>
</html>关键行与易错点
v-model="username":直接等号加变量名,不加冒号、不加引号包裹变量(写成v-model="'username'"就绑成字符串了)。v-model只对表单元素(输入框、下拉框、单选/复选框等)有意义,加在p、div这类标签上没有实际用途。- 双向的含义:数据变 → 输入框内容变;输入框输入 → 数据变。所以获取表单值不需要任何 DOM 操作,读
this.username即可。 - 重置表单不是去操作 input 元素,而是把数据置空(
this.username = ''),输入框自动清空;同理,修改数据即可实现数据回填。 - 易错点:把
v-model和v-bind混淆——v-bind是单向(数据 → 视图),只有v-model才是双向。
常见错误
点击按钮报
xxx is not a function,或控制台提示方法未定义 现象:模板里写了@click="del",点击无反应并报错。 原因:处理函数写进了data,或写在methods外(比如漏了逗号被吞进data对象里)。 修法:确认函数在methods配置项内、与data同级,且方法名与模板中的名字逐字一致。方法里
this.list是undefined现象:删除、切换逻辑全失效,this打印出来不是 Vue 实例。 原因:methods里用了箭头函数,this被绑定到外层作用域。 修法:methods内一律用普通函数del(id) { ... },需要固定this时在函数体内const vm = this或改用普通函数。传了参数后拿不到事件对象 现象:
@click="del(item.id)"里想用e.target,结果e是undefined。 原因:一旦显式传实参,Vue 不再自动注入事件对象。 修法:显式补上$event:@click="del($event, item.id)",形参按顺序接。图片不显示,审查元素发现
src="imgUrl"现象:属性值被当成普通字符串原样输出。 原因:漏写冒号,v-bind没生效,浏览器把变量名当路径。 修法:写成:src="imgUrl";判断标准是"这个值是变量还是字面量",变量就必须带冒号。v-for编译报错或整段不渲染 现象:控制台提示表达式非法,或列表一片空白。 原因:(item,index)in list少了空格,或把in写成of之外的错误词、把插值{{ }}漏掉直接写item.name。 修法:严格写成v-for="(item, index) in list",in前后留空格;文本用{{ item.name }},属性用:title="item.name"。删除第二条,页面却像删了最后一条;勾选框/背景色串行 现象:数据删对了,视图上的勾选状态、样式却错位到别的行。 原因:
v-for没加:key,Vue 就地复用 DOM,只把文本往前挪。 修法:加:key="item.id",值必须唯一且稳定;没有 id 时找一个不重复的字段,不要用index。点删除后数组变成
undefined或整表消失 现象:this.booksList = this.booksList.filter(...)之后列表全没了。 原因:箭头函数里漏了return,回调返回undefined被当作全不匹配;或把filter写成forEach。 修法:this.booksList = this.booksList.filter(item => item.id !== id),确认回调有返回值、条件方向是"留下不等于该 id 的"。图片切换点到底后图片空白,或下标变成 -1 现象:第一张再点上一页、最后一张再点下一页,图片裂开或控制台报错。 原因:只改了
index,没做边界限制,也没隐藏按钮。 修法:按钮加v-show="index > 0"与v-show="index < list.length - 1",或在方法内先判断范围再自增自减。v-model加在div/p上没反应 现象:输入框外的普通标签绑定后既不能改值也不报错。 原因:v-model只对表单元素(input/textarea/select/checkbox/radio)有意义。 修法:普通标签展示用{{ }}或v-text,需要"可编辑"就换成表单元素。输入框能打字,但页面上别处引用该变量不更新 现象:
v-model="username"正常,{{ username }}却始终为空。 原因:username没在data里声明,Vue 2 无法拦截后加的属性。 修法:所有参与绑定的字段先在data中初始化(哪怕是空字符串),不要运行时临时挂。
练习
计数器加固版 两个按钮实现
+1/-1,要求数值不能小于 0;同时把当前数值用属性绑定写到按钮的title上(如"当前 5")。请分别用"内联语句 +v-show"和"methods 方法内判断"两种写法各实现一次,比较哪种更容易扩展到"上限 10"。图片轮播增强 在切换图片案例基础上增加三件事:① 图片下方显示"第 x / 共 n 张";② 到第一张时上一页按钮置灰(用
:disabled绑定而不是隐藏);③ 到末尾点下一页时回到第一张(循环播放)。思考:改了第③点后,原来的v-show条件是否还需要?书架升级 给每本书加一个"标记已读/未读"按钮,点击后该书状态切换并在书名后显示【已读】;删除功能保持用
filter。要求:① 用:key且说明为什么不能用index;② 删除后已读状态不能串到别的书上,用勾选框或背景色验证一次。表单联动 登录页加一个"记住账号"复选框和"用户名不能为空"的提示:用户名为空时登录按钮禁用(
:disabled),勾选记住账号后点击登录把用户名存入另一个变量并在页面上回显。最后实现"重置"一次性清空输入框、复选框和回显内容——全程只操作数据。嵌套渲染 用
v-for渲染一个 3 行 × 5 列的表格,行数据形如[{ id:1, cells:[...] }]。要求两层循环都加:key,并说明内层 key 用什么值才唯一(提示:不能内外都用同一个id)。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 案例-切换图片(42-43) | 12:27 |
| 2 | v-for指令(44) | 05:50 |
| 3 | 案例-小黑的书架-渲染功能(45) | 03:46 |
| 4 | 案例-小黑的书架-删除功能(45) | 07:34 |
| 5 | v-for中的key(46-52) | 12:55 |
| 6 | v-model指令(53) | 10:43 |
小结
- 模板只负责"声明意图",一切分支判断、边界处理、数据变换都应下沉到
methods,模板里留一行表达式是上限。 - 判断该不该加冒号,只问一句"这个值是变量还是字面量",变量就必须是
:或v-bind:。 - 事件绑定的三条路径要能脱口而出:内联表达式 → 方法名(自动拿
e)→ 方法调用(必须显式传$event)。 - 一切增删改都改数据源,DOM 是数据的投影;
filter/splice的结果一定要重新赋值回去,视图才会跟着变。 :key是v-for的标配而非可选项,值必须唯一且稳定;index会随增删漂移,等于把"复用"变成"错位"。- 表单交互统一走
v-model,取值、清空、回填三件事本质上都是读写同一个 data 字段,不再需要手动操作value。 - 所有"用户可能点到尽头"的地方都要在模板层兜住:
v-show/v-if/:disabled/ 方法内范围判断,缺一个就会在演示时翻车。 - 排错顺序固定为:先看
this指向 → 再看配置项位置(data还是methods)→ 再看变量是否在data中声明 → 最后才怀疑指令拼写。