🐸 青蛙编程网
首页 / 前端 / Vue / 8. 综合练习:Vue 版待办清单

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 拓展练习

  1. localStorage 持久化:用 watch(tasks, save, { deep: true }),页面刷新不丢数据
  2. 筛选:加「全部/未完成/已完成」三个按钮,用 computed 按 filter 显示
  3. 双击编辑:夸张点,允许双击改文本(v-model + editingId)

8.5 进阶:想要工程化?(npm/Vite)

前 7 章为了让你先理解 Vue 本身,全程用了免构建写法。真实项目规模变大后,社区主流做法是工程化:用官方脚手架生成项目,获得热更新、单文件组件与打包优化。路线给出来,等你需要时再回头:

  1. 装 Node.js LTS 版 → npm -v 验证
  2. 建项目:npm create vue@latest my-vue-app(选项先一路 No)
  3. cd my-vue-app && npm install && npm run dev → 打开 http://localhost:5173/
  4. 对照免构建写法:

- createApp({...}).mount('#app') 依旧在 src/main.js 里 - 本教程的模板片段进 .vue 文件的 <template> - 组件对象换成 .vue 单文件组件(import 导入) - 免构建分散的 <style> 变成 <style scoped>(局部样式)

概念没变,多的是开发体验和工程约束——所以先把本教程的概念吃透再上。

8.6 小结

设计顺序永远是:数据结构 → 方法(改数据)→ 模板(显示数据)→ 样式。

组件坏习惯排雷:往模板里塞业务、往方法里塞 DOM 操作、把数据拆得零碎。

Vue 基础到此闭环。下一站 React,换一个思路相似的知名框架印证所学。

下一章:React 教程(第 1 章:React 是什么) 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。