Vue 三天学习怎么安排?用三个任务做出可交互的练习页面

编程狮(w3cschool.cn) 2026-09-10 10:38:56 浏览数 (23)
反馈

Vue 三天学习适合设成三个可验收的小任务:录入数据、筛选状态、展示统计,而不是三天掌握整个框架。本文把它们放在同一个任务列表里,让你看到输入如何改变状态,状态又如何更新页面。

Vue 三天练习把一份状态串成页面

你可能已经看懂模板插值,却一到自己写页面就不知道先建什么变量,也不确定什么时候需要计算属性。与其连续复制三个不相干的小案例,不如让同一份数据逐步承担更多交互。本文使用 Vue 3 的组合式 API,提供一个可以直接打开的完整页面;编程狮把目标限定为前端本地练习,不包含账户系统、后端保存或生产部署。每一天都有具体操作和预期结果,方便你判断自己是否真正跑通。

一、第一天先完成录入,不要从页面装饰开始

任务列表需要两类状态:输入框里的临时文字,以及已经提交的任务数组。临时文字可能为空、可能仍在编辑,不应该每敲一个字就直接追加到正式列表。点击添加或按回车时,才执行一次提交函数。

提交时先 trim 去掉首尾空白,空字符串直接返回;正常内容生成独立编号,再放进数组,最后清空输入。标题不是合适的唯一编号,因为你可能重复创建“复习数组”这类任务。示例使用单调递增的数字,足够支撑一次页面会话,但刷新后重新开始,不承担跨设备身份标识。

第一天的验收很简单:空白输入不能增加条目,输入一项后能看到它,回车和点击按钮的行为一致。先把这三个动作做好,再考虑按钮圆角。模板语法不熟悉时,可结合 Vue 3 教程 阅读 ref 与事件绑定的基础部分。

二、第二天增加筛选,但保留唯一的原始列表

增加完成状态后,页面需要展示全部、未完成和已完成三种视图。不要为三种视图分别维护三个数组,否则勾选一次任务就要同步修改多份数据,很容易产生计数不一致。

正确的出发点是保留 tasks 作为原始列表,filter 只保存当前筛选条件,再由 computed 计算 visibleTasks。筛选结果是派生视图,不应被当成另一份独立存档。切换按钮只改变条件,不删除原始数据。

复选框通过 v-model 修改每条任务的 done 属性。循环渲染时使用稳定的 id 作为 key,而不是数组下标;筛选之后同一条记录的位置可能变化,编号才表达它的身份。不要把“现在排第几项”当成“它是谁”。

第二天应该测试一个容易让人误解的情形:在“未完成”视图勾选任务,条目立即从当前列表消失。它不是被删除,而是进入已完成集合。切到“全部”或“已完成”能再次找到它,才说明筛选逻辑正确。

三、第三天加入统计,拼成一个完整页面

进度统计同样来自原始列表,而不是当前可见列表。否则切到“已完成”时总数会缩小,完成率看起来总是百分之百。示例用 completedCount 统计已完成数量,再以 tasks 的长度计算百分比,空列表明确返回零。

下面保存为 index.html 后在现代浏览器中打开。页面通过 CDN 加载固定版本 Vue 3.5.13,需要联网;如果浏览器或网络阻止外部脚本,应先解决加载问题。正式项目建议使用构建工具和生产资源,不沿用这个教学加载方式。

<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Vue 任务练习</title>
<style>
  * { box-sizing: border-box; }
  body { font: 16px/1.7 sans-serif; margin: 0; padding: 24px; background: #fff7f0; }
  main { max-width: 640px; margin: auto; padding: 24px; background: white; border-radius: 16px; }
  form { display: flex; gap: 8px; }
  input[type=text] { min-width: 0; flex: 1; padding: 10px; }
  button { padding: 8px 12px; cursor: pointer; }
  nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0; }
  button[aria-pressed=true] { background: #fe6a00; color: white; }
  ul { list-style: none; padding: 0; }
  li { padding: 8px 0; overflow-wrap: anywhere; }
  .done { text-decoration: line-through; color: #666; }
  progress { width: 100%; }
  [v-cloak] { display: none; }
</style>
<main id="app" v-cloak>
  <h1>三天任务练习</h1>
  <form @submit.prevent="addTask">
    <input type="text" v-model="draft" aria-label="新任务" placeholder="输入一项任务">
    <button type="submit">添加</button>
  </form>
  <nav aria-label="任务筛选">
    <button v-for="option in options" :key="option.value"
      :aria-pressed="filter === option.value"
      @click="filter = option.value">{{ option.label }}</button>
  </nav>
  <p role="status">总计 {{ tasks.length }} 项,完成 {{ completedCount }} 项({{ percent }}%)</p>
  <progress :value="completedCount" :max="tasks.length || 1" aria-label="任务进度"></progress>
  <ul>
    <li v-for="task in visibleTasks" :key="task.id">
      <label>
        <input type="checkbox" v-model="task.done">
        <span :class="{ done: task.done }">{{ task.title }}</span>
      </label>
    </li>
  </ul>
  <p v-if="visibleTasks.length === 0">当前视图没有任务。</p>
</main>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.13/dist/vue.global.js"></script>
<script>
const { createApp, ref, computed } = Vue;
createApp({
  setup() {
    const draft = ref("");
    const tasks = ref([]);
    const filter = ref("all");
    const options = [
      { value: "all", label: "全部" },
      { value: "active", label: "未完成" },
      { value: "done", label: "已完成" }
    ];
    let nextId = 1;
    function addTask() {
      const title = draft.value.trim();
      if (!title) return;
      tasks.value.push({ id: nextId++, title, done: false });
      draft.value = "";
    }
    const completedCount = computed(() => tasks.value.filter(t => t.done).length);
    const visibleTasks = computed(() => tasks.value.filter(t =>
      filter.value === "all" || (filter.value === "done" ? t.done : !t.done)
    ));
    const percent = computed(() => tasks.value.length
      ? Math.round(completedCount.value / tasks.value.length * 100) : 0);
    return { draft, tasks, filter, options, addTask, completedCount, visibleTasks, percent };
  }
}).mount("#app");
</script>
</html>

脚本里访问 ref 的值需要 .value,模板会自动解包,因此模板直接写 tasks.length。把脚本写法和模板写法混用,是初学阶段常见的错误。computed 读取了哪些响应式状态,就会跟踪对应依赖,不必再写一个监听器手动刷新百分比。

这里用插值显示标题,没有把用户输入作为 HTML 执行。即使输入看起来像标签,也会按文本展示。基础表单结构与语义可以参考 HTML 教程,不要为了省一个 label 而让复选框只能点击很小的方框。

四、用操作序列验收,再决定下一项扩展

请按顺序操作:添加“学习 ref”和“练习 computed”,总数应为二;勾选第一项,完成数应为一、进度为百分之五十;切换已完成视图,只显示第一项;取消勾选,它从当前视图消失,但总数仍为二;切回全部,两项仍在。

再补两个边界:输入全空格不新增;重复输入相同标题会创建不同编号的任务。后者是本文有意保留的产品规则,如果你需要标题去重,应先规定大小写与空白是否算相同,再在提交函数中判断,而不是靠 key 意外覆盖条目。

刷新后任务丢失也是预期,因为本例只保存在内存。下一步可以增加本地存储,但要处理旧数据解析失败、字段缺失和编号恢复。不要只加一行存储就认为完成持久化,也不要把本地存储误当成跨设备同步。

三天安排的价值在于限制范围:第一天确认提交动作,第二天确认派生列表,第三天确认跨视图统计。进度快可以继续拆组件,进度慢则保留同一个页面反复调试。不要把课程名里的时间承诺误读成你必须在固定时间掌握全部 Vue。

响应式与表单行为可对照 Vue 官方表单指南。本文页面刻意使用固定依赖与单文件入口,方便复现操作,不代表当前所有工程都应采用 CDN 模式。

筛选改变视图,不改变原始总数

总结

Vue 三天学习可以围绕一条数据流展开:输入提交到原始状态,计算属性生成筛选与统计,模板展示结果。这个页面虽小,却覆盖了事件、双向绑定、列表身份和派生状态之间的关系。

完成练习的判断不是复制出了页面,而是你能解释为什么切换筛选不改变总数、为什么复选框会让当前条目消失,以及刷新为什么丢失数据。解释清楚这三件事,再扩展删除、编辑和持久化会更稳。

延伸学习

  1. Vue.js 三天学习实战教程 继续安排练习,学习速度以实际掌握为准。
  2. JavaScript 教程 补齐数组筛选与函数基础。
  3. 阅读 Vue 与 React 选择笔记,从项目需求理解框架选择。

常见问题

Q:为什么模板里不用写 .value?

A:模板会自动解包这里暴露的 ref,而 setup 的脚本代码仍通过 .value 读写。不要把两处语法机械地复制到一起。

Q:筛选结果要再存一个 ref 吗?

A:本例不需要。它能由原始任务和筛选条件推导,使用 computed 可以减少重复状态及同步错误。

Q:三天之后就能独立完成业务系统吗?

A:本文只覆盖一个前端练习页面。接口、路由、权限、测试和部署仍需继续学习,三天是练习安排,不是能力保证。

0 人点赞