Vue 2 实战案例:记事本
案例介绍
本教程基于 Vue 2 框架,指导学习者实现一个功能完整的前端记事本应用——“Todo记事本”。通过本案例,学习者将系统掌握以下核心能力:
- 使用
v-for指令完成动态列表渲染,并正确绑定key属性 - 基于唯一标识符(ID)实现数据项的安全删除
- 运用数组高阶方法
filter()构建不可变式数据更新逻辑 - 理解索引(
index)与业务 ID 的语义差异及其在 UI 序号展示中的正确使用原则 - 遵循响应式数据更新规范,确保视图与状态严格同步
功能概览
小黑记事本包含四大核心功能模块:
- 任务列表渲染:将任务数据以结构化列表形式动态展示
- 任务删除:支持通过交互按钮移除指定任务项
- 任务新增:提供表单输入与提交机制,向列表追加新任务
- 任务统计与清空:实时显示任务总数,并支持一键清空全部任务
开发环境与初始结构
环境要求
- Vue 2.x(推荐 2.6.14 或以上稳定版本)
- 开发工具:VS Code(已配置 Vue 相关插件)
- 项目结构已初始化,包含基础 HTML 模板与 Vue 实例挂载点
初始模板结构
1 | <div id="app"> |
注意:上述代码为示意结构,实际开发中需在 Vue 实例中定义数据与方法后方可生效。
功能一:任务列表渲染
数据建模
在 Vue 实例的 data 选项中,定义任务列表 list。遵循数组包裹对象的数据结构规范,每个对象必须包含两个属性:
id:数值型唯一标识符,用于数据操作(如删除),不可重复且不可为空name:字符串类型的任务描述内容
1 | data() { |
关键原则:
id必须由开发者显式维护,不得依赖v-for的index参数生成或替代。index仅用于渲染序号等非持久化展示场景。
列表渲染指令
使用 v-for 指令遍历 list 数组,语法格式为:
1 | v-for="(item, index) in list" |
item:当前迭代的对象(即单个任务)index:当前项的零基索引(从 0 开始)list:待遍历的数据源(响应式数组)
key 属性的强制规范
v-for 必须配合 :key 绑定唯一、稳定、可预测的值。此处应绑定 item.id:
1 | <li v-for="(item, index) in list" :key="item.id"> |
禁止使用
:key="index"。因index在数组增删后会发生变化,导致 Vue 的虚拟 DOM Diff 算法失效,引发状态错乱与性能问题。
序号展示的正确实践
UI 中显示的序号(如 “1.”、“2.”)应基于 index 计算,而非 item.id:
1 | {{ index + 1 }}. {{ item.name }} |
原因分析:
item.id是业务主键,其值可能不连续(如删除 ID=2 后,剩余 ID 为 1 和 3)- 若直接使用
{{ item.id }},UI 将显示 “1.”、“3.”,违反用户对自然序号的预期 index反映当前渲染位置,index + 1恒为连续正整数,符合展示需求
功能二:任务删除
事件绑定与参数传递
为删除按钮绑定点击事件处理器,并将当前任务的 id 作为参数传入:
1 | <span @click="del(item.id)">×</span> |
禁止传递
index。因index具有时效性,在列表动态变化后不再准确对应原始数据项。
删除方法实现
在 methods 选项中定义 del 方法,接收 id 参数:
1 | methods: { |
核心逻辑解析
filter()方法创建一个新数组,不修改原数组,符合 Vue 响应式系统对数组变更检测的要求- 箭头函数
item => item.id !== id定义保留条件:仅当任务id不等于待删除id时,该任务被保留在新数组中 - 赋值语句
this.list = ...将过滤后的新数组重新赋给this.list,触发 Vue 的响应式更新机制
常见错误警示
| 错误写法 | 问题说明 |
|---|---|
this.list.splice(index, 1) |
依赖 index,删除后 index 失效;且 splice 直接修改原数组,虽可触发更新,但语义不清晰、易出错 |
delete this.list[index] |
delete 仅删除属性,不改变数组长度,导致空位(undefined),破坏响应式追踪 |
this.list = this.list.filter(item => item.id === id) |
逻辑反向 |
功能三:任务添加
本节将实现任务列表的新增功能,作为列表管理功能的重要组成部分。该功能需满足以下核心要求:
- 支持用户在输入框中输入任务名称;
- 点击“添加任务”按钮后,将新任务插入至任务列表最前端;
- 新增任务须具备唯一标识(
id)和名称(name)两个必需属性; - 输入为空时禁止提交,并给予明确提示;
- 提交成功后自动清空输入框。
关键设计原则:Vue 采用数据驱动视图机制。所有 DOM 更新均由数据变更触发,不得直接操作 DOM 元素。新增任务的本质是向响应式数组
list中插入新对象,视图将自动同步更新。
步骤一:使用 v-model 实现输入框双向绑定
为实时获取并同步用户输入内容,需将输入框与组件数据属性建立双向绑定关系。
在组件
data选项中声明一个字符串类型的数据属性:1
2
3
4
5
6
7data() {
return {
list: [
],
todoName: '' // 用于绑定输入框的响应式变量
}
}在模板中定位输入框元素(通常位于表单头部),为其添加
v-model指令:1
<input type="text" placeholder="请输入任务" v-model="todoName">
说明:
v-model是 Vue 的语法糖,等价于同时绑定:value和@input事件。绑定后,todoName的值将与输入框内容实时、双向同步。修改任一端,另一端立即响应。
步骤二:注册点击事件并实现数组前置插入
新增逻辑的核心是:点击按钮时,将新任务对象插入 list 数组最前端。
为“添加任务”按钮绑定
@click事件处理器:1
<button @click="addTodo">添加任务</button>
在组件
methods选项中定义addTodo方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18methods: {
addTodo() {
// 1. 输入校验:去除首尾空白后判断是否为空
if (this.todoName.trim() === '') {
alert('请输入任务名称');
return;
}
// 2. 构造新任务对象
const newTodo = {
id: +new Date(), // 使用时间戳确保 id 唯一性(数值型)
name: this.todoName
};
// 3. 使用 unshift() 方法将新对象插入数组最前端
this.list.unshift(newTodo);
// 4. 清空输入框
this.todoName = '';
}
}
重点说明:
unshift()是 JavaScript 数组原生方法,用于在数组开头添加一个或多个元素,并返回新长度;+new Date()将Date对象强制转换为毫秒级时间戳数值,可作为临时唯一id使用;this.todoName.trim() === ''用于排除仅含空白字符(空格、制表符、换行符)的无效输入;- 清空操作
this.todoName = ''依赖v-model的双向绑定特性,执行后输入框内容自动归零。
常见问题与注意事项
id的生成策略:当前使用时间戳是前端简易方案,适用于演示与原型开发。生产环境必须由后端服务分配全局唯一 ID(如 UUID、数据库自增主键),以避免并发冲突与数据一致性风险。unshift()与push()的选择:本教程采用unshift()实现“新增至顶部”,若需“新增至底部”,应替换为push()方法。key属性的必要性:v-for指令中必须为每个列表项指定唯一的:key(此处为item.id)。这是 Vue 渲染优化的关键机制,缺失将导致状态错乱与性能下降。- 输入校验的严谨性:
trim()方法仅去除首尾空白,不处理中间多余空格。如需更严格校验,可结合正则表达式(例如/^\s*$/)或引入表单验证库。 - 用户体验增强建议:实际项目中,应使用
Element UI或Ant Design Vue等组件库的Message组件替代原生alert(),以提供非阻塞、样式统一的提示反馈。
功能四:底部功能实现
本节完成小黑记事本应用的底部交互功能,包括任务数量统计与全部清空操作。所有实现均基于 Vue 2 的响应式数据驱动机制,严格遵循“数据变化 → 视图自动更新”的核心原则。
任务数量统计功能
- 在底部左侧区域动态显示当前待办任务总数;
- 该数值必须实时反映
list数组的实际长度; - 禁止使用静态数字(如硬编码为
2),须通过数据绑定实现动态计算。
实现原理
- Vue 2 中,数组实例的
length属性为响应式属性; - 当
list数组内容发生变化(如push、splice等操作)时,list.length的值将自动更新; - 模板中直接使用插值表达式
{{ list.length }}即可实现双向绑定。
1 | <div class="footer"> |
验证逻辑
- 初始状态:
list = [{...}, {...}]→ 显示合计:2; - 新增任务后(如调用
list.unshift({}))→list.length自动变为3→ 视图立即更新为合计:3; - 关键结论:
list.length是响应式数据源,无需额外监听或计算属性即可直接使用。
全部清空功能
- 点击“清空任务”按钮后,移除
list数组中所有元素; - 清空操作需触发视图同步更新,确保列表区域完全空白;
- 清空行为应直接修改原数组引用,而非仅创建新数组。
实现原理
- Vue 2 的响应式系统依赖于对原始数据对象的直接修改;
- 使用
this.list = []可直接替换原数组引用,触发依赖更新; - 禁止使用
this.list.splice(0)以外的非响应式方式(如this.list.length = 0在部分 Vue 2 版本中存在兼容性风险); - 推荐统一采用赋值空数组的方式,语义清晰且完全响应式。
1 | <div class="footer"> |
方法定义(JavaScript 部分)
1 | methods: { |
注意事项
- 不得使用
list = [](缺少this.前缀),否则无法触发响应式更新; - 不得使用
list.length = 0,该操作在 Vue 2 中不保证触发视图更新; - 所有对
list的修改必须通过this.list访问,确保被 Vue 的响应式系统追踪。
底部区域条件显示优化
- 当
list数组为空(即list.length === 0)时,整个底部区域(.footer)应隐藏; - 当
list数组非空时,底部区域应正常显示; - 该显示/隐藏状态需随
list长度动态切换,属于典型的条件渲染场景。
实现方案:v-show 指令
- 选用
v-show而非v-if,原因如下:v-show仅控制 CSSdisplay属性,切换开销小;- 底部结构简单,无复杂初始化逻辑,无需销毁/重建;
- 更符合频繁切换(增删清空)的性能需求。
模板代码(完整底部结构)
1 | <footer class="footer" v-show="list.length > 0"> |
行为验证
- 初始
list为空 → 底部区域不显示; - 添加任一任务 →
list.length > 0为真 → 底部显示; - 执行
clear()后list.length === 0→ 底部立即隐藏; - 核心要点:
v-show的表达式必须为布尔值,list.length > 0是最简洁可靠的判断方式。
功能总结与知识点回顾
本案例综合运用 Vue 2 核心指令与响应式机制,完整实现待办清单应用。各模块对应关键技术点如下:
列表渲染
- 使用
v-for指令遍历list数组; - 必须为每个列表项设置唯一
key属性,推荐使用item.id或index(仅限无序静态列表); - 通过插值表达式
{{ item.text }}渲染内容; - 数据驱动视图:
list变化 → 列表自动重绘。
删除功能
- 通过
@click事件绑定删除方法,并传递当前项索引或唯一标识; - 在方法体内调用
Array.prototype.filter()创建新数组; filter()返回新数组,必须显式赋值给this.list才能触发响应式更新;- 示例:
this.list = this.list.filter(item => item.id !== targetId);
添加功能
- 使用
v-model实现输入框双向绑定(绑定至data中的临时字段,如inputValue); - 在添加方法中调用
Array.prototype.unshift()将新任务插入数组头部; unshift()为原地修改方法,可直接触发响应式更新。
底部功能
- 任务统计依赖
list.length的响应式特性; - 清空操作通过
this.list = []直接重置数组引用; - 条件显示采用
v-show="list.length > 0"实现高效切换。
开发规范与最佳实践
- 所有数据操作必须通过
this.xxx访问,确保被 Vue 实例代理; - 避免直接修改数组 length 属性或使用非响应式方法(如
arr[0] = ...); - 条件渲染优先级:简单布尔判断 →
v-show;复杂结构或高开销组件 →v-if; - 模板中禁止出现业务逻辑运算,复杂表达式应提取至计算属性(
computed); - 本案例未使用计算属性,因
list.length本身已具备响应式能力,无需额外封装。
完整代码示例
1 |
|
