Skip to content

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 属性;表达式里写变量名即可,不要再套 &#123;&#123; &#125;&#125;。

语法与简写:从 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="&#123;&#123; imgUrl &#125;&#125;" 就是错误思路。第三,简写冒号不能丢::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,书名写 &#123;&#123; item.name &#125;&#125;,作者写 &#123;&#123; item.author &#125;&#125;。删除时不要直接操作 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 作为稳定标识。

本节易错点汇总 ​

  1. v-for 加在要循环的标签上,不要加在父级却以为会循环子级。
  2. (item, index) in arr 中 item 在前、index 在后。
  3. 遍历对象是 (value, key, index),遍历数字时 item 从 1 开始。
  4. 删除列表项要改数据,不要直接删 DOM。
  5. 循环列表必须重视 :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 前后要有空格;插值要用 &#123;&#123; &#125;&#125;。转写稿里说的“循环遍历这个 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。这样,渲染和删除两个功能就完成了。

课件图示 ​

课件第45页:图书管理案例- 小黑的书架 明确需求: ①基本渲染 ②删除功能 → v-for

课件第46页:v-for 中的key <ul> <li v-for="(item, index

课件第55页:综合案例- 小黑记事本 功能需求: ①列表渲染 ②删除功能 ③添加功能 ④底部统

课件第7页:Vue2 (6天) Vue 核心包开发 插值表达式 内置指令 侦听器 计算属性

课件第21页:插值表达式是一种Vue 的模板语法 1. 作用: 利用表达式进行插值,渲染到页面

课件第22页:总结 总结 1. 插值表达式的作用是什么? 利用表达式进行插值,将数据渲染页面中

课件第24页:我们已经掌握了基础的模板渲染,其实除了基本的模板渲染,Vue背后还做了大量工作。

课件第26页:总结 总结 1. 什么是响应式呢? 数据改变,视图自动更新 使用Vue 开发→

代码示例 ​

代码一:事件绑定 + 属性绑定 —— 图片切换案例 ​

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 才是双向。

常见错误 ​

  1. 点击按钮报 xxx is not a function,或控制台提示方法未定义 现象:模板里写了 @click="del",点击无反应并报错。 原因:处理函数写进了 data,或写在 methods 外(比如漏了逗号被吞进 data 对象里)。 修法:确认函数在 methods 配置项内、与 data 同级,且方法名与模板中的名字逐字一致。

  2. 方法里 this.list 是 undefined 现象:删除、切换逻辑全失效,this 打印出来不是 Vue 实例。 原因:methods 里用了箭头函数,this 被绑定到外层作用域。 修法:methods 内一律用普通函数 del(id) { ... },需要固定 this 时在函数体内 const vm = this 或改用普通函数。

  3. 传了参数后拿不到事件对象 现象:@click="del(item.id)" 里想用 e.target,结果 e 是 undefined。 原因:一旦显式传实参,Vue 不再自动注入事件对象。 修法:显式补上 $event:@click="del($event, item.id)",形参按顺序接。

  4. 图片不显示,审查元素发现 src="imgUrl" 现象:属性值被当成普通字符串原样输出。 原因:漏写冒号,v-bind 没生效,浏览器把变量名当路径。 修法:写成 :src="imgUrl";判断标准是"这个值是变量还是字面量",变量就必须带冒号。

  5. v-for 编译报错或整段不渲染 现象:控制台提示表达式非法,或列表一片空白。 原因:(item,index)in list 少了空格,或把 in 写成 of 之外的错误词、把插值 &#123;&#123; &#125;&#125; 漏掉直接写 item.name。 修法:严格写成 v-for="(item, index) in list",in 前后留空格;文本用 &#123;&#123; item.name &#125;&#125;,属性用 :title="item.name"。

  6. 删除第二条,页面却像删了最后一条;勾选框/背景色串行 现象:数据删对了,视图上的勾选状态、样式却错位到别的行。 原因:v-for 没加 :key,Vue 就地复用 DOM,只把文本往前挪。 修法:加 :key="item.id",值必须唯一且稳定;没有 id 时找一个不重复的字段,不要用 index。

  7. 点删除后数组变成 undefined 或整表消失 现象:this.booksList = this.booksList.filter(...) 之后列表全没了。 原因:箭头函数里漏了 return,回调返回 undefined 被当作全不匹配;或把 filter 写成 forEach。 修法:this.booksList = this.booksList.filter(item => item.id !== id),确认回调有返回值、条件方向是"留下不等于该 id 的"。

  8. 图片切换点到底后图片空白,或下标变成 -1 现象:第一张再点上一页、最后一张再点下一页,图片裂开或控制台报错。 原因:只改了 index,没做边界限制,也没隐藏按钮。 修法:按钮加 v-show="index > 0" 与 v-show="index < list.length - 1",或在方法内先判断范围再自增自减。

  9. v-model 加在 div/p 上没反应 现象:输入框外的普通标签绑定后既不能改值也不报错。 原因:v-model 只对表单元素(input/textarea/select/checkbox/radio)有意义。 修法:普通标签展示用 &#123;&#123; &#125;&#125; 或 v-text,需要"可编辑"就换成表单元素。

  10. 输入框能打字,但页面上别处引用该变量不更新 现象:v-model="username" 正常,&#123;&#123; username &#125;&#125; 却始终为空。 原因:username 没在 data 里声明,Vue 2 无法拦截后加的属性。 修法:所有参与绑定的字段先在 data 中初始化(哪怕是空字符串),不要运行时临时挂。

练习 ​

  1. 计数器加固版 两个按钮实现 +1 / -1,要求数值不能小于 0;同时把当前数值用属性绑定写到按钮的 title 上(如"当前 5")。请分别用"内联语句 + v-show"和"methods 方法内判断"两种写法各实现一次,比较哪种更容易扩展到"上限 10"。

  2. 图片轮播增强 在切换图片案例基础上增加三件事:① 图片下方显示"第 x / 共 n 张";② 到第一张时上一页按钮置灰(用 :disabled 绑定而不是隐藏);③ 到末尾点下一页时回到第一张(循环播放)。思考:改了第③点后,原来的 v-show 条件是否还需要?

  3. 书架升级 给每本书加一个"标记已读/未读"按钮,点击后该书状态切换并在书名后显示【已读】;删除功能保持用 filter。要求:① 用 :key 且说明为什么不能用 index;② 删除后已读状态不能串到别的书上,用勾选框或背景色验证一次。

  4. 表单联动 登录页加一个"记住账号"复选框和"用户名不能为空"的提示:用户名为空时登录按钮禁用(:disabled),勾选记住账号后点击登录把用户名存入另一个变量并在页面上回显。最后实现"重置"一次性清空输入框、复选框和回显内容——全程只操作数据。

  5. 嵌套渲染 用 v-for 渲染一个 3 行 × 5 列的表格,行数据形如 [{ id:1, cells:[...] }]。要求两层循环都加 :key,并说明内层 key 用什么值才唯一(提示:不能内外都用同一个 id)。

原视频对照 ​

#视频时长
1案例-切换图片(42-43)12:27
2v-for指令(44)05:50
3案例-小黑的书架-渲染功能(45)03:46
4案例-小黑的书架-删除功能(45)07:34
5v-for中的key(46-52)12:55
6v-model指令(53)10:43

小结 ​

  1. 模板只负责"声明意图",一切分支判断、边界处理、数据变换都应下沉到 methods,模板里留一行表达式是上限。
  2. 判断该不该加冒号,只问一句"这个值是变量还是字面量",变量就必须是 : 或 v-bind:。
  3. 事件绑定的三条路径要能脱口而出:内联表达式 → 方法名(自动拿 e)→ 方法调用(必须显式传 $event)。
  4. 一切增删改都改数据源,DOM 是数据的投影;filter / splice 的结果一定要重新赋值回去,视图才会跟着变。
  5. :key 是 v-for 的标配而非可选项,值必须唯一且稳定;index 会随增删漂移,等于把"复用"变成"错位"。
  6. 表单交互统一走 v-model,取值、清空、回填三件事本质上都是读写同一个 data 字段,不再需要手动操作 value。
  7. 所有"用户可能点到尽头"的地方都要在模板层兜住:v-show / v-if / :disabled / 方法内范围判断,缺一个就会在演示时翻车。
  8. 排错顺序固定为:先看 this 指向 → 再看配置项位置(data 还是 methods)→ 再看变量是否在 data 中声明 → 最后才怀疑指令拼写。