Skip to content

Vue 快速上手与指令(上)(四) ​

本页对应 Vue 快速上手与指令(上),共 6 段视频,约 46 分钟。

学习目标 ​

  • 能使用 v-for 完成小黑记事本列表渲染,正确绑定唯一 key,并展示“索引+1”的序号和任务名称。
  • 能使用 v-model 双向绑定输入框,点击添加时向数组新增包含唯一 id 和 name 的对象,并在添加后清空输入框。
  • 能通过点击事件传递 id 并完成删除功能,使用 filter 删除指定任务且保持其余数据不变。
  • 能使用 list.length 完成底部统计,并通过将数组置空实现清空任务,验证页面随数据变化自动更新。
  • 能说明本案例中增删清空均通过修改数据完成,不直接操作 DOM,并能正确区分 v-if、v-show、v-bind、v-on、v-model 的基本用法。

知识点 ​

案例目标与数据准备 ​

这个案例叫“小黑的书架”,目标很明确:左侧/数据区准备好书籍数组,右侧列表根据数组渲染出来;每行有一个“删除”按钮,点击后把当前这本书从列表中移除。效果如下:

小黑的书架案例效果

为什么需要先准备数据?因为 Vue 的核心思路是“数据驱动视图”,列表不是手写死的,而是由 booksList 渲染出来的。怎么想?先把书做成对象数组,每本书有唯一 id、书名 name、作者 author。怎么做?保留给定准备代码:

html
<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>

易错点:booksList 必须写在 data 里,模板里才能用;每本书的 id 不要重复,后面删除要靠它定位。

用 v-for 渲染书架列表 ​

为什么需要 v-for?因为有多条书籍数据,不能一条条手写 <li>。怎么想?把静态的 <li> 改成循环项,循环 booksList,每一项叫 item。怎么做?在 <li> 上加 v-for="item in booksList",并给 :key="item.id",内容用插值表达式:

html
<ul>
  <li v-for="item in booksList" :key="item.id">
    <span>{{ item.name }}</span>
    <span>{{ item.author }}</span>
    <button>删除</button>
  </li>
</ul>

转写稿里提到“循环遍历数据就可以了”“有 ID 优先用 ID,因为 ID 不重复”,这里正好用 item.id 做 key。易错点:v-for 不要写成 v-for="item in booksList" 之外的花样;key 要写在循环项上;模板里访问数据不要写 this.booksList,直接写 booksList。

给删除按钮绑定点击事件并传 id ​

为什么需要传 id?因为点击某一行的删除按钮时,方法要知道“删哪一本”。怎么想?按钮点击时调用删除方法,把当前项的 item.id 传过去。怎么做?用 @click="del(item.id)":

html
<button @click="del(item.id)">删除</button>

然后在 Vue 实例里写 methods,注意 methods 与 data 平级:

js
methods: {
  del(id) {
    this.booksList = this.booksList.filter(item => item.id !== id)
  }
}

转写稿里调试时说“点击的时候调一个方法,并且传递那个 ID”“方法千万不要写错位置,一定要和 data 平级”,这就是关键。易错点:@click="del(item.id)" 不要漏括号;方法名要和模板一致;不要写成 @click="del" 导致拿不到 id。

用 filter 按 id 过滤删除 ​

为什么用 filter?因为删除的本质是“保留 id 不等于当前 id 的项”,生成一个新数组再赋值回去。怎么想?filter 会遍历数组,返回满足条件的元素组成新数组。怎么做?写 this.booksList = this.booksList.filter(item => item.id !== id)。转写稿里也是这个思路:“filter 然后 item.id !== id”,并且“箭头函数返回一个”。易错点:不要只写 this.booksList.filter(...) 而不赋值,否则页面不会变;比较要用 !==;箭头函数参数别漏;删除后 Vue 会响应式更新列表。

完整代码与调试检查 ​

把上面拼起来:

html
<div id="app">
  <h3>小黑的书架</h3>
  <ul>
    <li v-for="item in booksList" :key="item.id">
      <span>{{ item.name }}</span>
      <span>{{ item.author }}</span>
      <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) {
        this.booksList = this.booksList.filter(item => item.id !== id)
      }
    }
  })
</script>

调试时先看列表是否渲染出 4 条,再点某行删除,确认对应行消失、其他行保留。易错点:methods 拼写、data 与 methods 层级、this.booksList 赋值、key 使用,都是本案例最常见的问题。

为什么需要 key ​

为什么需要: v-for 渲染列表时,Vue 默认会尽量“就地复用”已有元素,而不是把整个列表推倒重来。这样性能更好,但如果列表项顺序变化、插入或删除,复用就可能对应错元素。key 的作用就是给每个列表项一个唯一标识,让 Vue 能正确排序和复用。

怎么想: 转写稿里做小黑记事本时,列表用 v-for 循环 list,删除时传 id,添加时新增对象也要有 id。这说明列表项不是随便一段文字,而是有身份的数据。既然数据有 id,渲染时就应该把 id 交给 key,让 Vue 知道“谁是谁”。

怎么做: 在 v-for 的标签上写 :key="item.id"。注意是 :key,不是普通 key。因为 item.id 是变量表达式,要用 v-bind 绑定。笔记里的语法写作 key="唯一值",实际案例中应写成 :key="item.id"。

易错点: 不要写成 key="item.id",那样只是字符串;也不要见到属性就加冒号,转写稿总结里特别说:key 后边是变量才加冒号,class 后边不是变量就不加冒号。

语法与案例 ​

为什么需要: 只写 v-for 能渲染,但列表项缺少稳定身份。加上 key 后,删除、插入、排序时 Vue 才能按标识匹配。

怎么想: key 的值只能是字符串或数字,并且必须唯一。最推荐用数据里的 id,因为 id 不重复;不推荐用 index,因为索引会随着插入、删除变化,不能稳定对应原数据。

怎么做: 保留笔记中的代码:

js
<ul>
  <li v-for="(item, index) in booksList" :key="item.id">
    <span>{{ item.name }}</span>
    <span>{{ item.author }}</span>
    <button @click="del(item.id)">删除</button>
  </li>
</ul>

这里的 :key="item.id" 就是给每个 li 绑定唯一标识;删除按钮传 item.id,删除方法才能准确找到要删的那一项。

易错点: key 不要重复;不要用会变化的 index 当唯一标识;key 的值类型要稳定,不能一会儿数字一会儿对象。

结合小黑记事本看 key 的调试过程 ​

为什么需要: 转写稿的案例能帮助理解:列表渲染、删除、添加都围绕“唯一标识”展开。没有稳定 id,删除和复用都容易对不上。

怎么想: 视频里先循环 list,写 item, index,然后给 li 加 :key="item.id",并强调“有 ID 来说呢,是不是优先用 ID 啊?因为 ID 是不重复的”。删除时调 del(item.id),方法里用 filter 保留 item.id !== id 的项。添加时不能直接 unshift(this.name),否则新数据只有光秃秃的名字,格式和原数据不一致;要新增 { id: ..., name: this.name }。

怎么做: 删除逻辑可写成 this.list = this.list.filter(item => item.id !== id);添加时 id 用时间戳,转写稿提到 +new Date() 或 Date.now(),因为时间戳随时间变化,不容易重复。添加完再清空输入框:this.name = ''。

易错点: 新增对象的 id 不能写死,否则第二次添加会重复;也不能随便随机,因为随机仍可能撞值。key 重复会让复用判断出问题。转写稿还提醒:新增数据格式要和原数据一致,原来有 id、name,新增也要有。

易错点汇总 ​

为什么需要: key 写错不会总是立刻报错,但会在列表更新时埋下隐患。

怎么想: 记住三句话:唯一、稳定、用 id。index 虽然也是数字,但会变化,所以不推荐。

怎么做: 列表项有 id 就用 :key="item.id";删除传 id;添加生成不重复 id。

易错点: 第一,key 的值只能是字符串或数字;第二,必须唯一;第三,推荐 id,不推荐 index;第四,绑定变量别漏冒号。

双向绑定:让数据和页面互相“通电话” ​

前面学的插值表达式、v-text、v-html 都是单向的:数据变了,页面跟着更新。但表单元素有个特殊之处——它的值可以由用户在页面上直接改动。如果只靠单向渲染,用户在输入框里敲的内容就进不了 data,我们在代码里也就拿不到。

为什么需要:笔记给的定义是两条——数据改变后,呈现的页面结果会更新;页面结果更新后,数据也会随之而变。前一条我们已经在响应式里体验过了,后一条正是表单场景的刚需。

怎么想:把输入框的值和 data 里的某个变量“绑”在一起,谁变另一边都跟着变,就不用再手动去 document.querySelector 取 value 了。

怎么做:v-model="变量"。

易错点:v-model 只能用在表单元素上。转写稿里讲师说得很直白:只有表单元素才能在页面中改值,输入框你才能在上面改东西,一个 p 标签你怎么改,没法改。所以给 p、div 加 v-model 是没有意义的。

v-model 的语法和适用范围 ​

语法:v-model="变量"

作用:给表单元素(input、radio、select)使用,双向绑定数据,可以快速获取或设置表单元素内容。

转写稿里提到,这一节先讲基本使用(文本框),复选框、下拉列表怎么用 v-model,后面再继续说。

易错点:

  • 变量必须在 data 中声明。模板里用到的数据,data 中必须有,否则绑不上,这跟前面对插值表达式的要求是一致的。
  • 不要把 v-model 和 :value 混为一谈。:value 是 v-bind,只做单向的数据到页面。
  • 双向绑定能“设置”表单内容,靠的就是改数据:把变量改成什么,输入框里就显示什么。

案例需求:登录表单的获取与重置 ​

需求:

  1. 点击登录按钮获取表单中的内容
  2. 点击重置按钮清空表单中的内容

双向绑定案例效果图

笔记给出的初始结构如下,两个输入框和两个按钮,data 里已经准备好了 username 和 password:

js
<div id="app">
    账户:<input type="text"> <br><br>
    密码:<input type="password"> <br><br>
    <button>登录</button>
    <button>重置</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: ''
      },
    })
  </script>

把 v-model 和点击事件补上 ​

怎么想:既然 v-model 已经把输入框和变量绑在一起,那么“获取表单内容”就不需要再去读 DOM,直接读 this.username、this.password 就行;“清空表单”也不是去操作输入框,而是把这两个变量赋成空字符串,页面会自己变空。

怎么做:两个输入框分别加上 v-model,两个按钮分别加上点击事件,事件处理函数写在 methods 里。

html
<div id="app">
  账户:<input type="text" v-model="username"> <br><br>
  密码:<input type="password" v-model="password"> <br><br>
  <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>

易错点:methods 必须和 el、data 平级,不要嵌套进 data 里面写错位置;@click 后面跟的是方法名,方法要定义在 methods 中,两边名字要对得上。

用调试工具验证绑定是否真的通了 ​

转写稿里演示了一个很实用的验证过程:写好 v-model 之后,打开浏览器的调试工具查看 Vue 实例的数据,能看到 data 里确实有这个变量;接着在输入框里敲内容,比如输入“吃饭”,就能看到下边这个变量的值同步变成了“吃饭”——说明输入框的值已经被拿到了。

这个调试动作值得养成习惯:绑定没生效时,先看变量在不在、值有没有随输入变化,能快速判断是 v-model 写错了,还是变量根本没在 data 里声明。

重置为什么只改数据就够了 ​

转写稿里在讲记事本添加功能时提到过清空输入框的做法:让 this.name 等于空,这就是修改数据,输入框随之变空。这正是双向绑定“反向”的那一半在起作用。

怎么想:单向渲染时,要清空输入框得拿到 DOM 元素再改 value;有了双向绑定,数据是源头,改数据就是改页面。

易错点:重置时要把每个绑定的变量都置空,漏掉一个,对应的输入框就不会清空;另外不要写成 this.username == '',那是比较,不是赋值。

小结 ​

v-model 是 Vue 响应式特性在表单上的直接体现:页面改值,数据跟着变;数据改值,页面跟着变。用它之后,获取和设置表单内容都变成了“读变量”和“写变量”这两件最简单的事。

案例总览:四个功能串成一条线 ​

小黑记事本完成效果

这一节用一个完整的小案例把前面学的指令串起来,做出“小黑记事本”。它要完成四件事:

  1. 列表渲染
  2. 删除功能
  3. 添加功能
  4. 底部统计 和 清空

页面结构分三块:头部是一个 input 输入框加“添加任务”按钮;中间是 ul 列表区,li 里放序号、任务名称和删除按钮;底部是统计区和“清空任务”按钮。数据区已经准备好一个 list 数组,三条数据,每条都是 { id, name } 形式的小对象。

列表渲染:用 v-for 把数组铺到页面上 ​

为什么需要:模板里的 li 是静态写死的,只有一条。真实场景里任务数量不固定,写死就没法增删,必须让页面跟着数据走。

怎么想:既然数据在 list 数组里,就让 li 循环遍历这个数组,数组有几条就渲染几个 li。序号用索引,名称用 item.name。

怎么做:

html
<li v-for="(item, index) in list" :key="item.id">
  <span>{{ index + 1 }}</span>
  <span>{{ item.name }}</span>
  <button class="del" @click="del(item.id)">×</button>
</li>

易错点:一是 :key 优先用 id 而不是索引,因为 id 不会重复;二是索引从 0 开始,页面上要的是 1、2、3,所以必须写 index + 1,这一点在刷新页面对比 012 和 123 时最容易发现。

删除功能:点击后把这一条过滤掉 ​

为什么需要:列表能显示还不够,用户要能删掉不想要的任务。

怎么想:不需要操作 DOM,只改数据。点击时把这条的 id 传出去,在数组里把 id 相同的那条筛掉,数据一变页面自动更新,这就是响应式。

怎么做:

html
<button class="del" @click="del(item.id)">×</button>
js
methods: {
  del(id) {
    this.list = this.list.filter(item => item.id !== id)
  }
}

易错点:方法必须写在 methods 里,并且和 data 平级,转写稿里反复强调“千万不要写错位置”,写进 data 里就调不到。另外 filter 返回的是新数组,必须重新赋值给 this.list,只调用不接收是没有效果的。

添加功能:v-model 收集输入,unshift 插入 ​

为什么需要:记事本得能记录新任务,所以要把输入框里用户敲的内容拿到手。

怎么想:拿输入框的值用 v-model 做双向绑定最省事,输入什么,data 里的变量就跟着变;点“添加任务”时,往 list 里插一条新数据,页面自然多一行。

怎么做:data 里加一个 name: '',输入框绑上 v-model="name",按钮绑 @click="add":

html
<input v-model="name" type="text">
<button @click="add">添加任务</button>
js
data() {
  return {
    list: [...],
    name: ''
  }
},
methods: {
  add() {
    this.list.unshift({
      id: Date.now(),
      name: this.name
    })
    this.name = ''
  }
}

易错点(也是转写稿里的调试过程):一开始图省事写成 this.list.unshift(this.name),结果数组里多了“光秃秃的两个字”,和原来 { id, name } 的格式对不上,所以新增的也必须是对象。id 不能写死,比如固定写 23,加第二次就重复了;随机数也可能撞车;用时间戳 Date.now() 或 +new Date() 最稳,它随时间一直变化,不会重复。还有个小细节:添加完输入框没清空,得补一句 this.name = ''。用 unshift 是插到最前面,新任务显示在顶部,和原案例一致。

底部统计和清空 ​

为什么需要:用户要一眼看到还剩几条记录,也需要一键清空。

怎么想:统计就是数组长度,清空就是把数组变成空数组,两件事都短到不用单独写方法。

怎么做:

html
<span>合计:{{ list.length }}</span>
<button @click="list = []">清空任务</button>

易错点:内联写法里只能用 Vue 提供的数据和方法,写 console.log 这类是不行的;写长了还是老老实实抽到 methods 里。

小结 ​

整个案例没有一行 DOM 操作,全靠“改数据 → 视图自动更新”这一条思路。渲染靠 v-for,删除靠 filter 重新赋值,添加靠 v-model 收集加 unshift 插入,统计和清空直接读长度、赋空数组。把这四步走顺,指令部分就算真正落地了。

课件图示 ​

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

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

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

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

课件第44页:Vue 指令v-for 1. 作用:基于数据循环,多次渲染整个元素 2. 遍历数

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

课件第47页:v-for 中的key <li> </li> <li> </li> <li> <

课件第48页:v-for 中的key key作用:给元素添加的唯一标识。 booksList:

代码示例 ​

1. 小黑的书架:v-for 渲染 + key + 删除 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>小黑的书架</title>
</head>
<body>
  <div id="app">
    <h3>小黑的书架</h3>
    <ul>
      <li v-for="(item, index) in booksList" :key="item.id">
        <span>{{ item.name }}</span>
        <span>{{ item.author }}</span>
        <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) {
          this.booksList = this.booksList.filter(item => item.id !== id)
        }
      }
    })
  </script>
</body>
</html>

要点解释与易错点:

  • v-for="(item, index) in booksList" 用于循环渲染书籍列表。
  • :key="item.id" 推荐使用唯一 id,不推荐用 index 作为 key。
  • 删除按钮通过 @click="del(item.id)" 把当前行 id 传给方法。
  • filter 返回新数组,再赋值给 booksList,从而实现删除并触发视图更新。
  • 易错点:filter 条件应写 item.id !== id,不要直接修改原数组造成不可控问题。

2. 双向绑定:登录与重置 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>双向绑定</title>
</head>
<body>
  <div id="app">
    账户:<input type="text" v-model="username"> <br><br>
    密码:<input type="password" v-model="password"> <br><br>
    <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="password" 实现表单元素与数据的双向绑定。
  • 数据改变页面会更新;页面输入改变,数据也会随之改变。
  • 登录时通过 this.username、this.password 获取表单内容。
  • 重置时直接把数据赋值为空字符串,页面输入框会自动清空。
  • 易错点:v-model 主要用于表单元素;不要忘记在 data 中初始化变量。

3. 小黑记事本:列表渲染、删除、添加、统计、清空 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>小黑记事本</title>
</head>
<body>
  <div id="app">
    <header>
      <input type="text" v-model="name" placeholder="请输入任务">
      <button @click="add">添加任务</button>
    </header>

    <ul>
      <li v-for="(item, index) in list" :key="item.id">
        <span>{{ index + 1 }}.</span>
        <span>{{ item.name }}</span>
        <button @click="del(item.id)">×</button>
      </li>
    </ul>

    <footer>
      合计:{{ list.length }} 条
      <button @click="list = []">清空任务</button>
    </footer>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        list: [
          { id: 1, name: '吃饭' },
          { id: 2, name: '睡觉' },
          { id: 3, name: '打豆豆' }
        ],
        name: ''
      },
      methods: {
        add() {
          this.list.unshift({
            id: Date.now(),
            name: this.name
          })
          this.name = ''
        },
        del(id) {
          this.list = this.list.filter(item => item.id !== id)
        }
      }
    })
  </script>
</body>
</html>

要点解释与易错点:

  • 输入框使用 v-model="name" 获取任务名称。
  • 添加任务时用 unshift 把新任务加到数组前面,并且新增数据要保持对象格式:{ id, name }。
  • id 使用 Date.now(),避免使用随机数或固定值导致重复。
  • 添加完成后执行 this.name = '' 清空输入框。
  • 删除使用 filter 按 id 过滤;统计直接使用 list.length;清空直接让 list = []。
  • 易错点:不要只把 this.name 字符串直接加入数组,否则会破坏原有数据结构;v-for 的 :key 推荐使用唯一 id。

常见错误 ​

1. 新增任务时直接把输入框的字符串放进数组

  • 现象:点击添加后,新行只显示文字,作者、序号等字段全部为空,模板里 item.name 取不到值。
  • 原因:只想着“让数组多一条”,忘了数组元素必须和原有数据保持同样的对象结构。
  • 修法:新增时构造完整对象 { id: Date.now(), name: this.name },保证字段和已有数据对齐。

2. 新增数据的 id 用 index、随机数或写死的数字

  • 现象:连续添加几次后再删除,删掉的不是点中的那一行,或者一次删掉多行。
  • 原因:id 不唯一,filter 的比较条件同时命中了多条记录,key 也失去区分度。
  • 修法:用时间戳生成 id(Date.now() 或 +new Date()),它随时间递增,天然不重复。

3. filter 里用了花括号却忘记 return

  • 现象:点一次删除,整个列表被清空。
  • 原因:item => { item.id !== id } 的函数体返回 undefined,被当作 false,所有元素都被过滤掉。
  • 修法:去掉花括号写成 item => item.id !== id,或在花括号内补 return。

4. 添加成功后没有清空输入框

  • 现象:连续点击添加会重复插入同一内容,用户以为没生效。
  • 原因:只处理了“写入数组”,漏掉了“重置表单数据”。
  • 修法:在 add 方法末尾补 this.name = '',靠双向绑定自动清空视图。

5. v-model 绑定了 data 里没声明的变量,或绑到了非表单元素上

  • 现象:输入框打字没反应,或控制台报属性未定义。
  • 原因:v-model 只对 input、radio、select 等表单元素有效,且绑定的变量必须先在 data 中初始化。
  • 修法:data 里先写 name: '',再在 input 上写 v-model="name"。

6. 内联事件里写了 Vue 之外的东西

  • 现象:@click="console.log(1)" 直接报错。
  • 原因:内联语句的作用域是 Vue 实例,只能用实例上的数据和方法,console 不在其中。
  • 修法:逻辑超过一行或需要外部 API 时,抽成 methods 里的方法再调用。

7. key 用了 index

  • 现象:列表项里放输入框时,删掉中间一项后,后面几行的输入内容“串行”了。
  • 原因:index 会随删除而变化,无法稳定标识同一行,Vue 复用时把状态错配给了别的元素。
  • 修法:改用 :key="item.id"。

练习 ​

练习 1|书架空态 在小黑的书架案例中补两点:删除前用 confirm 二次确认;当 booksList.length === 0 时,列表区显示“书架空空如也”,否则正常渲染。

练习 2|添加校验 记事本的添加功能加上校验:输入内容 trim() 后为空则不插入,并给出提示;非空时正常插入到列表最前面,随后清空输入框。

练习 3|key 对比实验 把列表项结构改成“一个输入框 + 删除按钮”,先用 :key="index" 运行,在几个输入框里写不同内容后删除第二项,记录现象;再把 key 换成 item.id 重做一遍,用一句话写出两次结果差异的原因。

练习 4|完成任务 给每条记录加 done: false 字段,列表项前置一个复选框用 v-model="item.done" 绑定,已完成项文字加删除线;底部统计改为“共 X 条 / 已完成 Y 条”。

练习 5|按条件清空 把“清空任务”按钮改为“清空已完成”,点击后只移除 done 为 true 的记录,未完成的保留,并验证统计数字同步变化。

原视频对照 ​

#视频时长
1案例-小黑记事本-列表渲染05:15
2案例-小黑记事本-删除功能02:22
3案例-小黑记事本-添加功能11:31
4案例-小黑记事本-底部统计和清空01:44
5总结15:03
6获取资料和作业11:00

小结 ​

  1. 列表类需求的统一套路是:数据是唯一真相,增、删、改一律只动数组,视图交给响应式自动更新,全程不碰 DOM。
  2. 新增数据的正确性取决于“结构一致 + id 唯一”这两条,缺任何一条都会在渲染或删除环节暴露问题。
  3. 前端生成唯一 id,时间戳是成本最低的可靠方案;index、随机数、写死值都会在连续操作中失效。
  4. filter 的返回值直接决定删除结果,箭头函数简写与完整写法的差异必须心里有数。
  5. v-model 的价值在于“取值”和“设值”共用一份数据,因此清空表单只需要给变量重新赋值。
  6. key 不是用来消除警告的装饰品,它决定元素复用时状态归属于谁;能用 id 就不要用 index。
  7. 内联事件只适合一行以内的简单语句,涉及外部 API 或复杂逻辑一律下沉到 methods。
  8. 每个案例完成后都要跑边界:删中间项、连续添加、删到空、清空后再添加——正常流程往往掩盖问题。