8. 综合练习:Vue 版待办清单
这是什么:综合练习 = 同一个「待办清单」需求换一套技术再写一遍,本章免构建写法重做 jQuery 版那个项目。
为什么用:单独看每一章都会写,合起来却做不出东西。做完这一遍你才会发现:换框架变的只是描述语言,「数据先行」的思路一点没变。
如何用:本章先定好任务数组的数据结构,再写方法和模板,最后和 jQuery 版逐行对比着看差距在哪。
8.1 数据先行的设计
先想清楚「数据」长什么样,UI 全部跟着数据走:
<script>
const { createApp, ref, computed } = Vue
// 唯一的真相:任务数组
const tasks = ref([
{ id: 1, text: '学完 Vue', done: false },
{ id: 2, text: '写个组件', done: true }
])
const newTask = ref('') // 输入框内容
let nextId = 3
function addTask() {
const text = newTask.value.trim()
if (!text) return
tasks.value.push({ id: nextId++, text, done: false })
newTask.value = '' // 清空输入框
}
</script>
注意:没有一行代码碰 DOM。页面长什么样都由 tasks 在模板中 computed 出来。
8.2 完整页面(整段贴进运行场就能玩)
<!DOCTYPE html>
<html lang='zh-CN'>
<head>
<meta charset='utf-8'>
<title>Vue 待办清单</title>
<style>
.app { max-width: 420px; margin: 40px auto; font-family: system-ui, "Microsoft YaHei"; }
input { padding: 10px; width: 65%; }
button { padding: 10px 16px; background: #4caf50; color: #fff; border: none; cursor: pointer; }
ul { padding: 0; list-style: none; }
li { padding: 12px; margin-top: 8px; background: #f9f9f9; border-radius: 6px; cursor: pointer; }
li.done { text-decoration: line-through; color: #999; background: #eef7ee; }
.x { float: right; border: none; color: #f44336; background: none; font-size: 16px; }
.x:hover { background: #ffebee; border-radius: 50%; }
</style>
<script src='https://registry.npmmirror.com/vue/3.5.44/files/dist/vue.global.prod.js'></script>
</head>
<body>
<div id='app' class='app'>
<h1>Vue 待办清单</h1>
<input v-model='newTask' @keyup.enter='addTask' placeholder='输入任务' />
<button @click='addTask'>添加</button>
<!-- 完成度 -->
<p>已完成 {{ doneCount }} / {{ tasks.length }}({{ percent }}%)</p>
<ul>
<li
v-for='task in tasks'
:key='task.id'
:class='{ done: task.done }'
@click='task.done = !task.done'
>
{{ task.text }}
<button class='x' @click.stop='removeTask(task.id)'>×</button>
</li>
</ul>
</div>
<script>
const { createApp, ref, computed } = Vue
createApp({
setup() {
// 8.1 的数据与方法原样搬过来
const tasks = ref([
{ id: 1, text: '学完 Vue', done: false },
{ id: 2, text: '写个组件', done: true }
])
const newTask = ref('')
let nextId = 3
function addTask() {
const text = newTask.value.trim()
if (!text) return
tasks.value.push({ id: nextId++, text, done: false })
newTask.value = ''
}
function removeTask(id) {
tasks.value = tasks.value.filter((t) => t.id !== id)
}
// computed:由数据算出的显示值
const doneCount = computed(() =>
tasks.value.filter((t) => t.done).length
)
const percent = computed(() =>
tasks.value.length
? Math.round((doneCount.value / tasks.value.length) * 100)
: 0
)
return { tasks, newTask, addTask, removeTask, doneCount, percent }
}
}).mount('#app')
</script>
</body>
</html>
玩法:点任务本身划线/取消、点 × 删除、输入后按 Enter 或点「添加」新增、进度百分数实时变。
8.3 对比 jQuery 版
| 需求 | jQuery 做法 | Vue 做法 |
|---|---|---|
| 加任务 | 手工创建 li 字符串 + append | 改数组,v-for 自动渲染 |
| 删除 | 找到父节点逐层 fadeOut/remove | filter 改数组,DOM 自动更新 |
| 完成状态 | toggleClass 手工维护 | 直接改 task.done |
| 事件委托 | on('click', 'li', ...) 手动配 |
每个 li 自带 @click(模板里就写了) |
| 阻止冒泡 | e.stopPropagation() |
@click.stop |
| 统计完成数 | 每次操作手工重算并塞 DOM | computed 自动算 |
| 键盘 | e.key === 'Enter' 判断 |
@keyup.enter 修饰符 |
总结论:jQuery 手工管 DOM,Vue 只管数据。页面复杂得多时,这张对比表的差距会成倍放大。
8.4 拓展练习
- localStorage 持久化:用
watch(tasks, save, { deep: true }),页面刷新不丢数据 - 筛选:加「全部/未完成/已完成」三个按钮,用 computed 按 filter 显示
- 双击编辑:夸张点,允许双击改文本(v-model + editingId)
8.5 进阶:想要工程化?(npm/Vite)
前 7 章为了让你先理解 Vue 本身,全程用了免构建写法。真实项目规模变大后,社区主流做法是工程化:用官方脚手架生成项目,获得热更新、单文件组件与打包优化。路线给出来,等你需要时再回头:
- 装 Node.js LTS 版 →
npm -v验证 - 建项目:
npm create vue@latest my-vue-app(选项先一路 No) cd my-vue-app && npm install && npm run dev→ 打开http://localhost:5173/- 对照免构建写法:
- createApp({...}).mount('#app') 依旧在 src/main.js 里 - 本教程的模板片段进 .vue 文件的 <template> - 组件对象换成 .vue 单文件组件(import 导入) - 免构建分散的 <style> 变成 <style scoped>(局部样式)
概念没变,多的是开发体验和工程约束——所以先把本教程的概念吃透再上。
8.6 小结
设计顺序永远是:数据结构 → 方法(改数据)→ 模板(显示数据)→ 样式。
组件坏习惯排雷:往模板里塞业务、往方法里塞 DOM 操作、把数据拆得零碎。
Vue 基础到此闭环。下一站 React,换一个思路相似的知名框架印证所学。