From 343584849520381a428dde0e33abfe14aa8d23bd Mon Sep 17 00:00:00 2001 From: charlie Date: Mon, 23 Mar 2026 10:09:29 +0800 Subject: [PATCH] feat: add Vue2 exercises for dynamic styles, lifecycle methods, component communication, and course management dashboard - Implement dynamic styles and event handling in Vue2 with a card component. - Create lifecycle methods exercise to simulate async data loading and instance destruction. - Develop a component communication exercise with props, events, and slots. - Build a comprehensive course management dashboard with filtering, statistics, and component interactions. --- 06-typescript/src/main.ts | 47 +----- 07-vue2/01-vue-instance-and-data/README.md | 28 ++++ 07-vue2/01-vue-instance-and-data/answer.html | 24 +++ 07-vue2/01-vue-instance-and-data/answer.js | 14 ++ 07-vue2/01-vue-instance-and-data/starter.html | 24 +++ 07-vue2/01-vue-instance-and-data/starter.js | 15 ++ 07-vue2/02-template-bindings/README.md | 27 +++ 07-vue2/02-template-bindings/answer.html | 28 ++++ 07-vue2/02-template-bindings/answer.js | 16 ++ 07-vue2/02-template-bindings/starter.html | 28 ++++ 07-vue2/02-template-bindings/starter.js | 17 ++ 07-vue2/03-v-model-and-form/README.md | 31 ++++ 07-vue2/03-v-model-and-form/answer.html | 59 +++++++ 07-vue2/03-v-model-and-form/answer.js | 10 ++ 07-vue2/03-v-model-and-form/starter.html | 59 +++++++ 07-vue2/03-v-model-and-form/starter.js | 10 ++ .../README.md | 28 ++++ .../answer.html | 32 ++++ .../answer.js | 12 ++ .../starter.html | 32 ++++ .../starter.js | 12 ++ 07-vue2/05-computed-and-watch/README.md | 27 +++ 07-vue2/05-computed-and-watch/answer.html | 28 ++++ 07-vue2/05-computed-and-watch/answer.js | 30 ++++ 07-vue2/05-computed-and-watch/starter.html | 28 ++++ 07-vue2/05-computed-and-watch/starter.js | 26 +++ 07-vue2/06-class-style-and-event/README.md | 27 +++ 07-vue2/06-class-style-and-event/answer.html | 31 ++++ 07-vue2/06-class-style-and-event/answer.js | 14 ++ 07-vue2/06-class-style-and-event/starter.html | 31 ++++ 07-vue2/06-class-style-and-event/starter.js | 16 ++ 07-vue2/07-lifecycle-and-async/README.md | 31 ++++ 07-vue2/07-lifecycle-and-async/answer.html | 25 +++ 07-vue2/07-lifecycle-and-async/answer.js | 36 ++++ 07-vue2/07-lifecycle-and-async/starter.html | 25 +++ 07-vue2/07-lifecycle-and-async/starter.js | 30 ++++ .../README.md | 33 ++++ .../answer.html | 43 +++++ .../answer.js | 80 +++++++++ .../starter.html | 43 +++++ .../starter.js | 66 ++++++++ 07-vue2/09-final-course-dashboard/README.md | 44 +++++ 07-vue2/09-final-course-dashboard/answer.html | 66 ++++++++ 07-vue2/09-final-course-dashboard/answer.js | 99 +++++++++++ .../09-final-course-dashboard/starter.html | 65 +++++++ 07-vue2/09-final-course-dashboard/starter.js | 79 +++++++++ 07-vue2/README.md | 158 ++++++++++++++++++ README.md | 19 ++- 48 files changed, 1705 insertions(+), 48 deletions(-) create mode 100644 07-vue2/01-vue-instance-and-data/README.md create mode 100644 07-vue2/01-vue-instance-and-data/answer.html create mode 100644 07-vue2/01-vue-instance-and-data/answer.js create mode 100644 07-vue2/01-vue-instance-and-data/starter.html create mode 100644 07-vue2/01-vue-instance-and-data/starter.js create mode 100644 07-vue2/02-template-bindings/README.md create mode 100644 07-vue2/02-template-bindings/answer.html create mode 100644 07-vue2/02-template-bindings/answer.js create mode 100644 07-vue2/02-template-bindings/starter.html create mode 100644 07-vue2/02-template-bindings/starter.js create mode 100644 07-vue2/03-v-model-and-form/README.md create mode 100644 07-vue2/03-v-model-and-form/answer.html create mode 100644 07-vue2/03-v-model-and-form/answer.js create mode 100644 07-vue2/03-v-model-and-form/starter.html create mode 100644 07-vue2/03-v-model-and-form/starter.js create mode 100644 07-vue2/04-conditional-and-list-rendering/README.md create mode 100644 07-vue2/04-conditional-and-list-rendering/answer.html create mode 100644 07-vue2/04-conditional-and-list-rendering/answer.js create mode 100644 07-vue2/04-conditional-and-list-rendering/starter.html create mode 100644 07-vue2/04-conditional-and-list-rendering/starter.js create mode 100644 07-vue2/05-computed-and-watch/README.md create mode 100644 07-vue2/05-computed-and-watch/answer.html create mode 100644 07-vue2/05-computed-and-watch/answer.js create mode 100644 07-vue2/05-computed-and-watch/starter.html create mode 100644 07-vue2/05-computed-and-watch/starter.js create mode 100644 07-vue2/06-class-style-and-event/README.md create mode 100644 07-vue2/06-class-style-and-event/answer.html create mode 100644 07-vue2/06-class-style-and-event/answer.js create mode 100644 07-vue2/06-class-style-and-event/starter.html create mode 100644 07-vue2/06-class-style-and-event/starter.js create mode 100644 07-vue2/07-lifecycle-and-async/README.md create mode 100644 07-vue2/07-lifecycle-and-async/answer.html create mode 100644 07-vue2/07-lifecycle-and-async/answer.js create mode 100644 07-vue2/07-lifecycle-and-async/starter.html create mode 100644 07-vue2/07-lifecycle-and-async/starter.js create mode 100644 07-vue2/08-components-communication-and-final/README.md create mode 100644 07-vue2/08-components-communication-and-final/answer.html create mode 100644 07-vue2/08-components-communication-and-final/answer.js create mode 100644 07-vue2/08-components-communication-and-final/starter.html create mode 100644 07-vue2/08-components-communication-and-final/starter.js create mode 100644 07-vue2/09-final-course-dashboard/README.md create mode 100644 07-vue2/09-final-course-dashboard/answer.html create mode 100644 07-vue2/09-final-course-dashboard/answer.js create mode 100644 07-vue2/09-final-course-dashboard/starter.html create mode 100644 07-vue2/09-final-course-dashboard/starter.js create mode 100644 07-vue2/README.md diff --git a/06-typescript/src/main.ts b/06-typescript/src/main.ts index 19e5168..70eb316 100644 --- a/06-typescript/src/main.ts +++ b/06-typescript/src/main.ts @@ -1,10 +1,7 @@ import "./style.css"; import { lessons } from "./lessons"; -function getRequiredElement( - selector: string, - parent: ParentNode = document, -): T { +function getRequiredElement(selector: string, parent: ParentNode = document): T { const element = parent.querySelector(selector); if (!element) { @@ -35,34 +32,21 @@ app.innerHTML = `

-
+

知识点

    -
    -
    -

    运行结果

    - -
    -
    -
    -
    +

    Starter

    -
    -
    -

    Answer

    -
    -
    -
    @@ -73,10 +57,7 @@ const title = getRequiredElement("#lesson-title"); const focus = getRequiredElement("#lesson-focus"); const summary = getRequiredElement("#lesson-summary"); const pointList = getRequiredElement("#lesson-points"); -const output = getRequiredElement("#lesson-output"); const starterCode = getRequiredElement("#starter-code"); -const answerCode = getRequiredElement("#answer-code"); -const runButton = getRequiredElement("#run-demo"); let activeLessonId = lessons[0]?.id ?? ""; @@ -101,12 +82,6 @@ function renderLessonList(): void { .join(""); } -function renderOutput(lines: string[]): void { - output.innerHTML = lines - .map((line) => `
    ${line}
    `) - .join(""); -} - function renderActiveLesson(): void { const lesson = lessons.find((item) => item.id === activeLessonId); @@ -117,12 +92,8 @@ function renderActiveLesson(): void { title.textContent = `${lesson.id}. ${lesson.title}`; focus.textContent = lesson.focus; summary.textContent = lesson.summary; - pointList.innerHTML = lesson.keyPoints - .map((item) => `
  • ${item}
  • `) - .join(""); + pointList.innerHTML = lesson.keyPoints.map((item) => `
  • ${item}
  • `).join(""); starterCode.textContent = lesson.starterCode; - answerCode.textContent = lesson.answerCode; - renderOutput(lesson.runDemo()); renderLessonList(); } @@ -138,14 +109,4 @@ nav.addEventListener("click", (event) => { renderActiveLesson(); }); -runButton.addEventListener("click", () => { - const lesson = lessons.find((item) => item.id === activeLessonId); - - if (!lesson) { - return; - } - - renderOutput(lesson.runDemo()); -}); - renderActiveLesson(); diff --git a/07-vue2/01-vue-instance-and-data/README.md b/07-vue2/01-vue-instance-and-data/README.md new file mode 100644 index 0000000..e720aad --- /dev/null +++ b/07-vue2/01-vue-instance-and-data/README.md @@ -0,0 +1,28 @@ +# 练习 1:Vue 实例和数据 + +## 目标 + +学会写一个最基础的 Vue2 实例,并让页面内容跟着数据变化。 + +## 你要练什么 + +- `new Vue()` +- `el` +- `data` +- `methods` +- 模板插值 `{{ }}` + +## 任务 + +请基于页面结构完成以下操作: + +- 显示课程标题和章节数量 +- 点击按钮后让学习人数加 1 +- 在控制台输出当前学习人数 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/answer.js) diff --git a/07-vue2/01-vue-instance-and-data/answer.html b/07-vue2/01-vue-instance-and-data/answer.html new file mode 100644 index 0000000..25c47f9 --- /dev/null +++ b/07-vue2/01-vue-instance-and-data/answer.html @@ -0,0 +1,24 @@ + + + + + + Vue 实例和数据 + + + +
    +

    {{ title }}

    +

    本章共有 {{ lessonCount }} 个小节。

    +

    当前学习人数:{{ learnerCount }}

    + +
    + + + + + diff --git a/07-vue2/01-vue-instance-and-data/answer.js b/07-vue2/01-vue-instance-and-data/answer.js new file mode 100644 index 0000000..2b5e8de --- /dev/null +++ b/07-vue2/01-vue-instance-and-data/answer.js @@ -0,0 +1,14 @@ +new Vue({ + el: "#app", + data: { + title: "Vue2 基础入门", + lessonCount: 8, + learnerCount: 12, + }, + methods: { + increaseLearner() { + this.learnerCount += 1; + console.log("当前学习人数:", this.learnerCount); + }, + }, +}); diff --git a/07-vue2/01-vue-instance-and-data/starter.html b/07-vue2/01-vue-instance-and-data/starter.html new file mode 100644 index 0000000..f3e2f53 --- /dev/null +++ b/07-vue2/01-vue-instance-and-data/starter.html @@ -0,0 +1,24 @@ + + + + + + Vue 实例和数据 + + + +
    +

    {{ title }}

    +

    本章共有 {{ lessonCount }} 个小节。

    +

    当前学习人数:{{ learnerCount }}

    + +
    + + + + + diff --git a/07-vue2/01-vue-instance-and-data/starter.js b/07-vue2/01-vue-instance-and-data/starter.js new file mode 100644 index 0000000..1da1125 --- /dev/null +++ b/07-vue2/01-vue-instance-and-data/starter.js @@ -0,0 +1,15 @@ +new Vue({ + el: "#app", + data: { + title: "Vue2 基础入门", + lessonCount: 8, + learnerCount: 12, + }, + methods: { + increaseLearner() { + // 任务: + // 1. learnerCount 加 1 + // 2. 在控制台输出最新人数 + }, + }, +}); diff --git a/07-vue2/02-template-bindings/README.md b/07-vue2/02-template-bindings/README.md new file mode 100644 index 0000000..dd38a1d --- /dev/null +++ b/07-vue2/02-template-bindings/README.md @@ -0,0 +1,27 @@ +# 练习 2:模板语法和绑定 + +## 目标 + +学会把数据绑定到属性和事件上,而不是手动改 DOM。 + +## 你要练什么 + +- `{{ }}` +- `v-bind` +- `v-on` +- 事件方法 + +## 任务 + +请基于页面结构完成以下操作: + +- 显示课程名和讲师名 +- 绑定图片地址和课程链接 +- 点击按钮后切换收藏状态 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/answer.js) diff --git a/07-vue2/02-template-bindings/answer.html b/07-vue2/02-template-bindings/answer.html new file mode 100644 index 0000000..7e46d06 --- /dev/null +++ b/07-vue2/02-template-bindings/answer.html @@ -0,0 +1,28 @@ + + + + + + 模板绑定 + + + +
    +

    {{ courseTitle }}

    +

    讲师:{{ teacher }}

    + + 查看课程详情 +

    当前状态:{{ isCollected ? "已收藏" : "未收藏" }}

    + +
    + + + + + diff --git a/07-vue2/02-template-bindings/answer.js b/07-vue2/02-template-bindings/answer.js new file mode 100644 index 0000000..439405f --- /dev/null +++ b/07-vue2/02-template-bindings/answer.js @@ -0,0 +1,16 @@ +new Vue({ + el: "#app", + data: { + courseTitle: "Vue2 模板语法", + teacher: "李老师", + coverUrl: "https://picsum.photos/800/320?random=21", + courseLink: "https://cn.vuejs.org/", + isCollected: false, + }, + methods: { + toggleCollect() { + this.isCollected = !this.isCollected; + console.log("收藏状态:", this.isCollected ? "已收藏" : "未收藏"); + }, + }, +}); diff --git a/07-vue2/02-template-bindings/starter.html b/07-vue2/02-template-bindings/starter.html new file mode 100644 index 0000000..53bbfc6 --- /dev/null +++ b/07-vue2/02-template-bindings/starter.html @@ -0,0 +1,28 @@ + + + + + + 模板绑定 + + + +
    +

    {{ courseTitle }}

    +

    讲师:{{ teacher }}

    + + 查看课程详情 +

    当前状态:{{ isCollected ? "已收藏" : "未收藏" }}

    + +
    + + + + + diff --git a/07-vue2/02-template-bindings/starter.js b/07-vue2/02-template-bindings/starter.js new file mode 100644 index 0000000..3c4ef15 --- /dev/null +++ b/07-vue2/02-template-bindings/starter.js @@ -0,0 +1,17 @@ +new Vue({ + el: "#app", + data: { + courseTitle: "Vue2 模板语法", + teacher: "李老师", + coverUrl: "https://picsum.photos/800/320?random=21", + courseLink: "https://cn.vuejs.org/", + isCollected: false, + }, + methods: { + toggleCollect() { + // 任务: + // 1. 切换 isCollected + // 2. 在控制台输出最新收藏状态 + }, + }, +}); diff --git a/07-vue2/03-v-model-and-form/README.md b/07-vue2/03-v-model-and-form/README.md new file mode 100644 index 0000000..2f077dc --- /dev/null +++ b/07-vue2/03-v-model-and-form/README.md @@ -0,0 +1,31 @@ +# 练习 3:v-model 和表单 + +## 目标 + +学会用 `v-model` 处理输入、选择和实时预览。 + +## 你要练什么 + +- `v-model` +- 输入框 +- 多行文本 +- 下拉框 +- 单选框 +- 复选框 + +## 任务 + +请基于页面结构完成以下操作: + +- 输入昵称并实时显示 +- 输入学习目标并实时显示 +- 选择当前阶段并显示结果 +- 选择偏好的学习节奏 +- 勾选已经掌握的基础能力 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/answer.js) diff --git a/07-vue2/03-v-model-and-form/answer.html b/07-vue2/03-v-model-and-form/answer.html new file mode 100644 index 0000000..c30fe64 --- /dev/null +++ b/07-vue2/03-v-model-and-form/answer.html @@ -0,0 +1,59 @@ + + + + + + v-model 和表单 + + + +
    +
    + + + +
    +

    偏好的学习节奏

    + + +
    +
    +

    已掌握的基础能力

    + + + +
    +
    + +
    +

    实时预览

    +

    昵称:{{ nickname || "未填写" }}

    +

    学习目标:{{ goal || "还没有输入目标" }}

    +

    当前阶段:{{ stage }}

    +

    学习节奏:{{ pace }}

    +

    已掌握:{{ skills.length ? skills.join("、") : "还没有勾选" }}

    +
    +
    + + + + + diff --git a/07-vue2/03-v-model-and-form/answer.js b/07-vue2/03-v-model-and-form/answer.js new file mode 100644 index 0000000..37e6632 --- /dev/null +++ b/07-vue2/03-v-model-and-form/answer.js @@ -0,0 +1,10 @@ +new Vue({ + el: "#app", + data: { + nickname: "林晨", + goal: "希望独立完成一个 Vue2 管理后台页面。", + stage: "进阶", + pace: "每周集中学习", + skills: ["HTML", "CSS"], + }, +}); diff --git a/07-vue2/03-v-model-and-form/starter.html b/07-vue2/03-v-model-and-form/starter.html new file mode 100644 index 0000000..44c6ea2 --- /dev/null +++ b/07-vue2/03-v-model-and-form/starter.html @@ -0,0 +1,59 @@ + + + + + + v-model 和表单 + + + +
    +
    + + + +
    +

    偏好的学习节奏

    + + +
    +
    +

    已掌握的基础能力

    + + + +
    +
    + +
    +

    实时预览

    +

    昵称:{{ nickname }}

    +

    学习目标:{{ goal }}

    +

    当前阶段:{{ stage }}

    +

    学习节奏:{{ pace }}

    +

    已掌握:{{ skills.join("、") }}

    +
    +
    + + + + + diff --git a/07-vue2/03-v-model-and-form/starter.js b/07-vue2/03-v-model-and-form/starter.js new file mode 100644 index 0000000..fe6fdfb --- /dev/null +++ b/07-vue2/03-v-model-and-form/starter.js @@ -0,0 +1,10 @@ +new Vue({ + el: "#app", + data: { + nickname: "", + goal: "", + stage: "入门", + pace: "每天学习", + skills: [], + }, +}); diff --git a/07-vue2/04-conditional-and-list-rendering/README.md b/07-vue2/04-conditional-and-list-rendering/README.md new file mode 100644 index 0000000..aa26e8b --- /dev/null +++ b/07-vue2/04-conditional-and-list-rendering/README.md @@ -0,0 +1,28 @@ +# 练习 4:条件渲染和列表渲染 + +## 目标 + +学会根据状态显示不同内容,并用 `v-for` 渲染一组数据。 + +## 你要练什么 + +- `v-if` +- `v-else` +- `v-show` +- `v-for` +- `:key` + +## 任务 + +请基于页面结构完成以下操作: + +- 切换“是否展开课程列表” +- 根据是否付费显示不同文案 +- 用 `v-for` 渲染课程数组 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/answer.js) diff --git a/07-vue2/04-conditional-and-list-rendering/answer.html b/07-vue2/04-conditional-and-list-rendering/answer.html new file mode 100644 index 0000000..94e6292 --- /dev/null +++ b/07-vue2/04-conditional-and-list-rendering/answer.html @@ -0,0 +1,32 @@ + + + + + + 条件渲染和列表渲染 + + + +
    +

    Vue2 渲染练习

    + + +

    你已经开通会员课程。

    +

    你还没有开通会员课程。

    + +
      +
    • + {{ course.title }} - {{ course.status }} +
    • +
    +
    + + + + + diff --git a/07-vue2/04-conditional-and-list-rendering/answer.js b/07-vue2/04-conditional-and-list-rendering/answer.js new file mode 100644 index 0000000..2815d60 --- /dev/null +++ b/07-vue2/04-conditional-and-list-rendering/answer.js @@ -0,0 +1,12 @@ +new Vue({ + el: "#app", + data: { + isPaid: true, + showList: true, + courses: [ + { id: 1, title: "Vue 实例", status: "已完成" }, + { id: 2, title: "指令系统", status: "学习中" }, + { id: 3, title: "组件通信", status: "未开始" }, + ], + }, +}); diff --git a/07-vue2/04-conditional-and-list-rendering/starter.html b/07-vue2/04-conditional-and-list-rendering/starter.html new file mode 100644 index 0000000..66a8780 --- /dev/null +++ b/07-vue2/04-conditional-and-list-rendering/starter.html @@ -0,0 +1,32 @@ + + + + + + 条件渲染和列表渲染 + + + +
    +

    Vue2 渲染练习

    + + +

    你已经开通会员课程。

    +

    你还没有开通会员课程。

    + +
      +
    • + {{ course.title }} - {{ course.status }} +
    • +
    +
    + + + + + diff --git a/07-vue2/04-conditional-and-list-rendering/starter.js b/07-vue2/04-conditional-and-list-rendering/starter.js new file mode 100644 index 0000000..771d9c8 --- /dev/null +++ b/07-vue2/04-conditional-and-list-rendering/starter.js @@ -0,0 +1,12 @@ +new Vue({ + el: "#app", + data: { + isPaid: false, + showList: true, + courses: [ + { id: 1, title: "Vue 实例", status: "已完成" }, + { id: 2, title: "指令系统", status: "学习中" }, + { id: 3, title: "组件通信", status: "未开始" }, + ], + }, +}); diff --git a/07-vue2/05-computed-and-watch/README.md b/07-vue2/05-computed-and-watch/README.md new file mode 100644 index 0000000..a352fe1 --- /dev/null +++ b/07-vue2/05-computed-and-watch/README.md @@ -0,0 +1,27 @@ +# 练习 5:computed 和 watch + +## 目标 + +学会区分“根据已有数据推导结果”和“监听变化做额外动作”。 + +## 你要练什么 + +- `computed` +- `watch` +- 数据推导 +- 监听输入变化 + +## 任务 + +请基于页面结构完成以下操作: + +- 根据关键字过滤课程列表 +- 用 `computed` 计算过滤结果数量 +- 用 `watch` 在关键字变化时输出日志 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/answer.js) diff --git a/07-vue2/05-computed-and-watch/answer.html b/07-vue2/05-computed-and-watch/answer.html new file mode 100644 index 0000000..b6b0f26 --- /dev/null +++ b/07-vue2/05-computed-and-watch/answer.html @@ -0,0 +1,28 @@ + + + + + + computed 和 watch + + + +
    +

    课程搜索

    + +

    匹配数量:{{ matchedCount }}

    +
      +
    • + {{ item.title }} +
    • +
    +
    + + + + + diff --git a/07-vue2/05-computed-and-watch/answer.js b/07-vue2/05-computed-and-watch/answer.js new file mode 100644 index 0000000..cd04847 --- /dev/null +++ b/07-vue2/05-computed-and-watch/answer.js @@ -0,0 +1,30 @@ +new Vue({ + el: "#app", + data: { + keyword: "", + courses: [ + { id: 1, title: "Vue 实例入门" }, + { id: 2, title: "Vue 指令系统" }, + { id: 3, title: "组件通信实战" }, + ], + }, + computed: { + filteredCourses() { + const value = this.keyword.trim().toLowerCase(); + + if (!value) { + return this.courses; + } + + return this.courses.filter((item) => item.title.toLowerCase().includes(value)); + }, + matchedCount() { + return this.filteredCourses.length; + }, + }, + watch: { + keyword(newValue) { + console.log("关键字变化:", newValue); + }, + }, +}); diff --git a/07-vue2/05-computed-and-watch/starter.html b/07-vue2/05-computed-and-watch/starter.html new file mode 100644 index 0000000..39fc4df --- /dev/null +++ b/07-vue2/05-computed-and-watch/starter.html @@ -0,0 +1,28 @@ + + + + + + computed 和 watch + + + +
    +

    课程搜索

    + +

    匹配数量:{{ matchedCount }}

    +
      +
    • + {{ item.title }} +
    • +
    +
    + + + + + diff --git a/07-vue2/05-computed-and-watch/starter.js b/07-vue2/05-computed-and-watch/starter.js new file mode 100644 index 0000000..cfcef9a --- /dev/null +++ b/07-vue2/05-computed-and-watch/starter.js @@ -0,0 +1,26 @@ +new Vue({ + el: "#app", + data: { + keyword: "", + courses: [ + { id: 1, title: "Vue 实例入门" }, + { id: 2, title: "Vue 指令系统" }, + { id: 3, title: "组件通信实战" }, + ], + }, + computed: { + filteredCourses() { + // 任务:返回过滤后的课程列表 + return this.courses; + }, + matchedCount() { + // 任务:返回 filteredCourses 的数量 + return 0; + }, + }, + watch: { + keyword(newValue) { + // 任务:在控制台输出关键字变化 + }, + }, +}); diff --git a/07-vue2/06-class-style-and-event/README.md b/07-vue2/06-class-style-and-event/README.md new file mode 100644 index 0000000..6032051 --- /dev/null +++ b/07-vue2/06-class-style-and-event/README.md @@ -0,0 +1,27 @@ +# 练习 6:动态类名、样式和事件 + +## 目标 + +学会让组件状态直接影响样式和交互反馈。 + +## 你要练什么 + +- `:class` +- `:style` +- `v-on` +- 点击切换状态 + +## 任务 + +请基于页面结构完成以下操作: + +- 点击卡片切换激活状态 +- 根据激活状态切换类名 +- 根据进度值动态改变进度条宽度 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/answer.js) diff --git a/07-vue2/06-class-style-and-event/answer.html b/07-vue2/06-class-style-and-event/answer.html new file mode 100644 index 0000000..4cb3fa9 --- /dev/null +++ b/07-vue2/06-class-style-and-event/answer.html @@ -0,0 +1,31 @@ + + + + + + 动态类名、样式和事件 + + + +
    +
    +

    {{ title }}

    +

    {{ isActive ? "当前卡片已激活" : "点击卡片激活它" }}

    +
    +
    +
    +

    当前进度:{{ progress }}%

    +
    +
    + + + + + diff --git a/07-vue2/06-class-style-and-event/answer.js b/07-vue2/06-class-style-and-event/answer.js new file mode 100644 index 0000000..9dd5cc0 --- /dev/null +++ b/07-vue2/06-class-style-and-event/answer.js @@ -0,0 +1,14 @@ +new Vue({ + el: "#app", + data: { + title: "动态样式练习", + isActive: false, + progress: 35, + }, + methods: { + toggleCard() { + this.isActive = !this.isActive; + this.progress = this.isActive ? 80 : 35; + }, + }, +}); diff --git a/07-vue2/06-class-style-and-event/starter.html b/07-vue2/06-class-style-and-event/starter.html new file mode 100644 index 0000000..2967698 --- /dev/null +++ b/07-vue2/06-class-style-and-event/starter.html @@ -0,0 +1,31 @@ + + + + + + 动态类名、样式和事件 + + + +
    +
    +

    {{ title }}

    +

    {{ isActive ? "当前卡片已激活" : "点击卡片激活它" }}

    +
    +
    +
    +

    当前进度:{{ progress }}%

    +
    +
    + + + + + diff --git a/07-vue2/06-class-style-and-event/starter.js b/07-vue2/06-class-style-and-event/starter.js new file mode 100644 index 0000000..1e74241 --- /dev/null +++ b/07-vue2/06-class-style-and-event/starter.js @@ -0,0 +1,16 @@ +new Vue({ + el: "#app", + data: { + title: "动态样式练习", + isActive: false, + progress: 35, + }, + methods: { + toggleCard() { + // 任务: + // 1. 切换 isActive + // 2. 如果激活了,让 progress 增加到 80 + // 3. 如果取消激活,让 progress 回到 35 + }, + }, +}); diff --git a/07-vue2/07-lifecycle-and-async/README.md b/07-vue2/07-lifecycle-and-async/README.md new file mode 100644 index 0000000..39df62a --- /dev/null +++ b/07-vue2/07-lifecycle-and-async/README.md @@ -0,0 +1,31 @@ +# 练习 7:生命周期和异步更新 + +## 目标 + +学会在合适的生命周期里加载数据,并理解页面更新的时机。 + +## 你要练什么 + +- `created` +- `mounted` +- `updated` +- `beforeDestroy` +- `destroyed` +- 模拟异步请求 + +## 任务 + +请基于页面结构完成以下操作: + +- 进入页面时显示 loading +- 在 `mounted` 里模拟异步获取课程数据 +- 数据返回后渲染课程列表 +- 在 `updated` 里输出更新日志 +- 点击按钮时销毁当前实例,并观察销毁日志 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/answer.js) diff --git a/07-vue2/07-lifecycle-and-async/answer.html b/07-vue2/07-lifecycle-and-async/answer.html new file mode 100644 index 0000000..0afe6ea --- /dev/null +++ b/07-vue2/07-lifecycle-and-async/answer.html @@ -0,0 +1,25 @@ + + + + + + 生命周期和异步更新 + + + +
    +

    生命周期练习

    + +

    数据加载中...

    +
      +
    • {{ item.title }}
    • +
    +
    + + + + + diff --git a/07-vue2/07-lifecycle-and-async/answer.js b/07-vue2/07-lifecycle-and-async/answer.js new file mode 100644 index 0000000..d902b09 --- /dev/null +++ b/07-vue2/07-lifecycle-and-async/answer.js @@ -0,0 +1,36 @@ +new Vue({ + el: "#app", + data: { + loading: true, + courses: [], + }, + created() { + console.log("created: 实例已经创建"); + }, + mounted() { + console.log("mounted: 页面已经挂载"); + + setTimeout(() => { + this.courses = [ + { id: 1, title: "生命周期基础" }, + { id: 2, title: "异步数据渲染" }, + { id: 3, title: "组件更新时机" }, + ]; + this.loading = false; + }, 1000); + }, + updated() { + console.log("updated: 页面数据已经更新"); + }, + beforeDestroy() { + console.log("beforeDestroy: 实例即将销毁"); + }, + destroyed() { + console.log("destroyed: 实例已经销毁"); + }, + methods: { + destroyInstance() { + this.$destroy(); + }, + }, +}); diff --git a/07-vue2/07-lifecycle-and-async/starter.html b/07-vue2/07-lifecycle-and-async/starter.html new file mode 100644 index 0000000..0a0e774 --- /dev/null +++ b/07-vue2/07-lifecycle-and-async/starter.html @@ -0,0 +1,25 @@ + + + + + + 生命周期和异步更新 + + + +
    +

    生命周期练习

    + +

    数据加载中...

    +
      +
    • {{ item.title }}
    • +
    +
    + + + + + diff --git a/07-vue2/07-lifecycle-and-async/starter.js b/07-vue2/07-lifecycle-and-async/starter.js new file mode 100644 index 0000000..03fcabf --- /dev/null +++ b/07-vue2/07-lifecycle-and-async/starter.js @@ -0,0 +1,30 @@ +new Vue({ + el: "#app", + data: { + loading: true, + courses: [], + }, + created() { + console.log("created: 实例已经创建"); + }, + mounted() { + // 任务: + // 1. 模拟异步请求 + // 2. 1 秒后给 courses 赋值 + // 3. loading 改成 false + }, + updated() { + // 任务:在控制台输出 updated 日志 + }, + beforeDestroy() { + // 任务:在控制台输出 beforeDestroy 日志 + }, + destroyed() { + // 任务:在控制台输出 destroyed 日志 + }, + methods: { + destroyInstance() { + // 任务:调用 this.$destroy() + }, + }, +}); diff --git a/07-vue2/08-components-communication-and-final/README.md b/07-vue2/08-components-communication-and-final/README.md new file mode 100644 index 0000000..dca2597 --- /dev/null +++ b/07-vue2/08-components-communication-and-final/README.md @@ -0,0 +1,33 @@ +# 练习 8:组件通信与综合练习 + +## 目标 + +把 Vue2 最核心的组件能力串起来,完成一个小型页面。 + +## 你要练什么 + +- 组件注册(全局 / 局部) +- `props` +- `props` 校验 +- `$emit` +- `slot` +- `ref` + +## 任务 + +请基于页面结构完成以下操作: + +- 把课程卡片拆成子组件 +- 注册一个全局角标组件 +- 父组件通过 `props` 传课程数据 +- 给 `props` 补上类型、必填、默认值或校验规则 +- 子组件点击按钮后通过 `$emit` 通知父组件切换状态 +- 用 `slot` 自定义按钮文案 +- 用 `ref` 聚焦输入框 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/answer.js) diff --git a/07-vue2/08-components-communication-and-final/answer.html b/07-vue2/08-components-communication-and-final/answer.html new file mode 100644 index 0000000..08d13a8 --- /dev/null +++ b/07-vue2/08-components-communication-and-final/answer.html @@ -0,0 +1,43 @@ + + + + + + 组件通信与综合练习 + + + +
    +
    +

    Vue2 综合练习

    + + +
    + +
    + + + +
    +
    + + + + + diff --git a/07-vue2/08-components-communication-and-final/answer.js b/07-vue2/08-components-communication-and-final/answer.js new file mode 100644 index 0000000..d516f93 --- /dev/null +++ b/07-vue2/08-components-communication-and-final/answer.js @@ -0,0 +1,80 @@ +Vue.component("course-badge", { + props: { + label: { + type: String, + default: "默认角标", + }, + }, + template: `{{ label }}`, +}); + +const CourseCard = { + props: { + course: { + type: Object, + required: true, + }, + theme: { + type: String, + default: "normal", + validator(value) { + return ["normal", "accent"].includes(value); + }, + }, + }, + template: ` +
    + +

    {{ course.title }}

    +

    + {{ course.finished ? "已完成" : "学习中" }} +

    + +
    + `, +}; + +new Vue({ + el: "#app", + components: { + CourseCard, + }, + data: { + keyword: "", + courses: [ + { id: 1, title: "Vue 实例", finished: true, level: "基础" }, + { id: 2, title: "模板语法", finished: false, level: "基础" }, + { id: 3, title: "组件通信", finished: false, level: "进阶" }, + ], + }, + computed: { + filteredCourses() { + const value = this.keyword.trim().toLowerCase(); + + if (!value) { + return this.courses; + } + + return this.courses.filter((course) => course.title.toLowerCase().includes(value)); + }, + }, + methods: { + toggleCourse(courseId) { + this.courses = this.courses.map((course) => { + if (course.id === courseId) { + return { + ...course, + finished: !course.finished, + }; + } + + return course; + }); + }, + focusInput() { + this.$refs.keywordInput.focus(); + }, + }, +}); diff --git a/07-vue2/08-components-communication-and-final/starter.html b/07-vue2/08-components-communication-and-final/starter.html new file mode 100644 index 0000000..45936f7 --- /dev/null +++ b/07-vue2/08-components-communication-and-final/starter.html @@ -0,0 +1,43 @@ + + + + + + 组件通信与综合练习 + + + +
    +
    +

    Vue2 综合练习

    + + +
    + +
    + + + +
    +
    + + + + + diff --git a/07-vue2/08-components-communication-and-final/starter.js b/07-vue2/08-components-communication-and-final/starter.js new file mode 100644 index 0000000..84bc61d --- /dev/null +++ b/07-vue2/08-components-communication-and-final/starter.js @@ -0,0 +1,66 @@ +Vue.component("course-badge", { + props: { + label: { + type: String, + default: "默认角标", + }, + }, + template: `{{ label }}`, +}); + +const CourseCard = { + props: { + course: { + type: Object, + required: true, + }, + theme: { + type: String, + default: "normal", + validator(value) { + return ["normal", "accent"].includes(value); + }, + }, + }, + template: ` +
    + +

    {{ course.title }}

    +

    + {{ course.finished ? "已完成" : "学习中" }} +

    + +
    + `, +}; + +new Vue({ + el: "#app", + components: { + CourseCard, + }, + data: { + keyword: "", + courses: [ + { id: 1, title: "Vue 实例", finished: true, level: "基础" }, + { id: 2, title: "模板语法", finished: false, level: "基础" }, + { id: 3, title: "组件通信", finished: false, level: "进阶" }, + ], + }, + computed: { + filteredCourses() { + // 任务:根据 keyword 过滤课程列表 + return this.courses; + }, + }, + methods: { + toggleCourse(courseId) { + // 任务:根据 courseId 切换 finished + }, + focusInput() { + // 任务:通过 ref 聚焦输入框 + }, + }, +}); diff --git a/07-vue2/09-final-course-dashboard/README.md b/07-vue2/09-final-course-dashboard/README.md new file mode 100644 index 0000000..130e05f --- /dev/null +++ b/07-vue2/09-final-course-dashboard/README.md @@ -0,0 +1,44 @@ +# 练习 9:Vue2 综合课程管理面板 + +## 目标 + +把 Vue2 入门阶段最关键的能力串起来,完成一个带搜索、筛选、统计、组件通信的课程管理面板。 + +## 你要练什么 + +- `new Vue()` +- `data` +- `methods` +- `computed` +- `watch` +- `v-model` +- `v-if` +- `v-show` +- `v-for` +- `:key` +- `:class` +- `props` +- `props` 校验 +- `$emit` +- `slot` +- `ref` + +## 任务 + +请基于页面结构完成以下操作: + +- 输入关键字过滤课程名称 +- 用下拉框切换“全部 / 已完成 / 学习中” +- 点击按钮让搜索框获得焦点 +- 用子组件渲染每一项课程 +- 子组件通过 `$emit` 通知父组件切换完成状态 +- 没有匹配结果时显示空状态 +- 页面底部显示总数、已完成数、当前筛选数 +- 在 `watch` 里输出关键字变化日志 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/starter.js) +- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/answer.html) +- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/answer.js) diff --git a/07-vue2/09-final-course-dashboard/answer.html b/07-vue2/09-final-course-dashboard/answer.html new file mode 100644 index 0000000..367bd78 --- /dev/null +++ b/07-vue2/09-final-course-dashboard/answer.html @@ -0,0 +1,66 @@ + + + + + + Vue2 综合课程管理面板 + + + +
    +
    +

    Vue2 课程管理面板

    +
    + + + +
    +
    + +
    +
    暂无匹配结果
    + +
    + + + +
    +
    + +
    +
    总课程数:{{ totalCount }}
    +
    已完成数:{{ finishedCount }}
    +
    当前筛选数:{{ visibleCount }}
    +
    +
    + + + + + diff --git a/07-vue2/09-final-course-dashboard/answer.js b/07-vue2/09-final-course-dashboard/answer.js new file mode 100644 index 0000000..5fcc620 --- /dev/null +++ b/07-vue2/09-final-course-dashboard/answer.js @@ -0,0 +1,99 @@ +Vue.component("level-badge", { + props: { + level: { + type: String, + default: "基础", + }, + }, + computed: { + badgeClass() { + return this.level === "进阶" ? "badge advanced" : "badge"; + }, + }, + template: `{{ level }}`, +}); + +const CourseItem = { + props: { + course: { + type: Object, + required: true, + }, + }, + template: ` +
    +
    + + {{ course.title }} +
    +

    {{ course.finished ? "当前已完成" : "当前学习中" }}

    + +
    + `, +}; + +new Vue({ + el: "#app", + components: { + CourseItem, + }, + data: { + keyword: "", + statusFilter: "all", + courses: [ + { id: 1, title: "Vue 实例基础", finished: true, level: "基础" }, + { id: 2, title: "模板语法与指令", finished: false, level: "基础" }, + { id: 3, title: "组件通信实战", finished: false, level: "进阶" }, + { id: 4, title: "生命周期和异步", finished: true, level: "进阶" }, + ], + }, + computed: { + filteredCourses() { + const keyword = this.keyword.trim().toLowerCase(); + + return this.courses.filter((course) => { + const matchStatus = + this.statusFilter === "all" || + (this.statusFilter === "done" && course.finished) || + (this.statusFilter === "doing" && !course.finished); + + const matchKeyword = !keyword || course.title.toLowerCase().includes(keyword); + + return matchStatus && matchKeyword; + }); + }, + totalCount() { + return this.courses.length; + }, + finishedCount() { + return this.courses.filter((course) => course.finished).length; + }, + visibleCount() { + return this.filteredCourses.length; + }, + }, + watch: { + keyword(newValue) { + console.log("搜索关键字变化:", newValue); + }, + }, + methods: { + toggleCourse(courseId) { + this.courses = this.courses.map((course) => { + if (course.id === courseId) { + return { + ...course, + finished: !course.finished, + }; + } + + return course; + }); + }, + focusSearch() { + this.$refs.searchInput.focus(); + }, + }, +}); diff --git a/07-vue2/09-final-course-dashboard/starter.html b/07-vue2/09-final-course-dashboard/starter.html new file mode 100644 index 0000000..7caa718 --- /dev/null +++ b/07-vue2/09-final-course-dashboard/starter.html @@ -0,0 +1,65 @@ + + + + + + Vue2 综合课程管理面板 + + + +
    +
    +

    Vue2 课程管理面板

    +
    + + + +
    +
    + +
    +
    暂无匹配结果
    + +
    + + + +
    +
    + +
    +
    总课程数:{{ totalCount }}
    +
    已完成数:{{ finishedCount }}
    +
    当前筛选数:{{ visibleCount }}
    +
    +
    + + + + + diff --git a/07-vue2/09-final-course-dashboard/starter.js b/07-vue2/09-final-course-dashboard/starter.js new file mode 100644 index 0000000..8e77a61 --- /dev/null +++ b/07-vue2/09-final-course-dashboard/starter.js @@ -0,0 +1,79 @@ +Vue.component("level-badge", { + props: { + level: { + type: String, + default: "基础", + }, + }, + template: `{{ level }}`, +}); + +const CourseItem = { + props: { + course: { + type: Object, + required: true, + }, + }, + template: ` +
    +
    + + {{ course.title }} +
    +

    {{ course.finished ? "当前已完成" : "当前学习中" }}

    + +
    + `, +}; + +new Vue({ + el: "#app", + components: { + CourseItem, + }, + data: { + keyword: "", + statusFilter: "all", + courses: [ + { id: 1, title: "Vue 实例基础", finished: true, level: "基础" }, + { id: 2, title: "模板语法与指令", finished: false, level: "基础" }, + { id: 3, title: "组件通信实战", finished: false, level: "进阶" }, + { id: 4, title: "生命周期和异步", finished: true, level: "进阶" }, + ], + }, + computed: { + filteredCourses() { + // 任务: + // 1. 先按 statusFilter 过滤 + // 2. 再按 keyword 过滤标题 + return this.courses; + }, + totalCount() { + return this.courses.length; + }, + finishedCount() { + // 任务:返回已完成课程数量 + return 0; + }, + visibleCount() { + // 任务:返回当前筛选后的数量 + return 0; + }, + }, + watch: { + keyword(newValue) { + // 任务:在控制台输出关键字变化 + }, + }, + methods: { + toggleCourse(courseId) { + // 任务:根据 courseId 切换 finished + }, + focusSearch() { + // 任务:通过 ref 聚焦输入框 + }, + }, +}); diff --git a/07-vue2/README.md b/07-vue2/README.md new file mode 100644 index 0000000..0b8cc7f --- /dev/null +++ b/07-vue2/README.md @@ -0,0 +1,158 @@ +# Vue2(理解框架思想) + +这部分只解决一个问题:你能不能从“手写 DOM”切换到“数据驱动视图”的思维方式,并理解 Vue2 为什么能让页面开发更高效。 + +## 学完后你应该掌握 + +- Vue2 是什么,为什么说它是渐进式框架 +- `new Vue()`、`el`、`data`、`methods` 的基础写法 +- 模板插值 `{{ }}` +- `v-bind` 和 `v-on` +- `v-model` 做表单双向绑定,包括文本、单选和复选 +- `v-if`、`v-show`、`v-for`、`:key` +- `computed` 和 `watch` 的基本区别 +- `:class` 和 `:style` 的动态绑定 +- 生命周期里的 `created`、`mounted`、`updated`、`beforeDestroy`、`destroyed` +- 组件、`props`、`props` 校验、`$emit` +- 插槽 `slot` +- `ref` 的基本用法 +- 如何把这些能力组合成一个 Vue2 小页面 + +## 这一章在解决什么 + +Vue2 不是教你再多学一套“新语法”,而是帮你把页面开发的思路改掉。 + +它回答的是: + +- 页面内容为什么应该由数据决定 +- 一个点击事件为什么不一定要手动改 DOM +- 一段重复结构为什么应该交给 `v-for` +- 一块页面为什么应该拆成组件 +- 父组件和子组件之间怎么传数据和传事件 + +## 必须建立的 6 个核心意识 + +### 1. 视图应该跟着数据走 + +不是先找 DOM 再改内容,而是先改数据,让页面自动更新。 + +### 2. 指令是模板和数据之间的桥 + +像 `v-bind`、`v-on`、`v-model`、`v-if`、`v-for` 这些指令,本质上是在描述“数据怎么影响页面”。 + +### 3. 计算属性更适合“基于已有数据得出结果” + +如果一个值可以从别的数据推导出来,优先考虑 `computed`。 + +### 4. `watch` 更适合“监听变化后做副作用” + +比如发请求、写日志、同步别的状态。 + +### 5. 组件化不是拆文件,而是拆职责 + +一个组件只关心一块明确的界面和交互。 + +### 6. 父子通信是 Vue 开发主线 + +- 父组件通过 `props` 往下传数据 +- 子组件通过 `$emit` 往上通知事件 + +## 全部知识点清单 + +### 基础认知 + +- Vue2 渐进式框架 +- 数据驱动视图 +- 声明式渲染 + +### 实例基础 + +- `new Vue()` +- `el` +- `data` +- `methods` + +### 模板与指令 + +- `{{ }}` +- `v-bind` +- `v-on` +- `v-model` +- `radio` +- `checkbox` +- `v-if` +- `v-else` +- `v-show` +- `v-for` +- `:key` + +### 响应式进阶 + +- `computed` +- `watch` +- `:class` +- `:style` + +### 生命周期 + +- `created` +- `mounted` +- `updated` +- `beforeDestroy` +- `destroyed` + +### 组件通信 + +- 组件注册(全局 / 局部) +- `props` +- `props` 校验 +- `$emit` +- `slot` +- `ref` +- 综合页面组织能力 + +## 学习顺序 + +1. Vue 实例和数据绑定 +2. 模板语法和事件绑定 +3. 表单双向绑定 +4. 条件渲染和列表渲染 +5. `computed` 和 `watch` +6. 动态类名、样式和事件交互 +7. 生命周期和异步更新 +8. 组件通信 +9. 综合课程管理面板 + +## 练习目录 + +- [01-vue-instance-and-data/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/README.md) +- [02-template-bindings/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/README.md) +- [03-v-model-and-form/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/README.md) +- [04-conditional-and-list-rendering/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/README.md) +- [05-computed-and-watch/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/README.md) +- [06-class-style-and-event/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/README.md) +- [07-lifecycle-and-async/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/README.md) +- [08-components-communication-and-final/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/README.md) +- [09-final-course-dashboard/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/README.md) + +## 过关标准 + +如果你能独立做到下面这些,就说明这一章已经基本过关: + +- 能写出一个基础 Vue2 实例 +- 能用模板语法展示数据 +- 能用 `v-model` 处理输入 +- 能用 `v-model` 处理单选框和复选框 +- 能用 `v-if`、`v-show`、`v-for` 组织页面 +- 能分清 `computed` 和 `watch` +- 能写出基础组件并使用 `props` / `props` 校验 / `$emit` +- 能理解插槽和 `ref` 的常见用途 +- 能知道 Vue 实例销毁前后会经过哪些生命周期 +- 能完成一个带组件通信的小页面 + +## 学习建议 + +- 学 Vue2 时先不要急着记所有 API,先抓住“数据变,页面跟着变”这条主线 +- 遇到页面不更新时,先看是不是数据没有改到 +- 遇到组件通信问题时,先分清“数据往下传”还是“事件往上抛” +- 学组件时先从 2 个组件开始,不要一上来拆太细 diff --git a/README.md b/README.md index 9ba6c92..4fba497 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ - `04-dom-events-async`:预留给 DOM + 事件 + 异步 - `05-es6-plus`:预留给 ES6+(现代 JS) - `06-typescript`:预留给 TypeScript +- `07-vue2`:预留给 Vue2 ## 当前可学内容 @@ -56,7 +57,14 @@ - `answer.ts` 参考答案 - `Vite + TypeScript` 学习面板 -前六部分现在都已经补充到“核心主线 + 常见细分知识点”。 +现在也已经整理好 `07-vue2`,里面包含: + +- Vue2(理解框架思想)讲义 +- 分阶段练习 +- `starter.html` / `starter.js` 起始代码 +- `answer.html` / `answer.js` 参考答案 + +前七部分现在都已经补充到“核心主线 + 常见细分知识点”。 ## 使用方式 @@ -66,9 +74,10 @@ 4. 再阅读 [04-dom-events-async/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/README.md) 5. 再阅读 [05-es6-plus/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/README.md) 6. 再阅读 [06-typescript/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/06-typescript/README.md) -7. 按顺序完成每个练习目录 -8. 先写 `starter.html`、`starter.css`、`starter.js` 或 `starter.ts` -9. 写完后再对照答案文件 -10. 学 `06-typescript` 时,也可以进入 [06-typescript](/Users/lijiaqing/home/wwwroot/front-end-example/06-typescript) 后执行 `npm install` 和 `npm run dev` +7. 再阅读 [07-vue2/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/README.md) +8. 按顺序完成每个练习目录 +9. 先写 `starter.html`、`starter.css`、`starter.js` 或 `starter.ts` +10. 写完后再对照答案文件 +11. 学 `06-typescript` 时,也可以进入 [06-typescript](/Users/lijiaqing/home/wwwroot/front-end-example/06-typescript) 后执行 `npm install` 和 `npm run dev` 如果你后面要继续学其他知识点,我可以按同样结构继续给你补更多工程化目录。