From d0d8be443b1edd56177ad972c24fb77a42bd82cf Mon Sep 17 00:00:00 2001 From: charlie Date: Tue, 24 Mar 2026 23:02:58 +0800 Subject: [PATCH] feat: Add Vue3 exercises and interview plan - Introduced Vue3 exercises covering composable API, reactivity, lifecycle hooks, and built-in components. - Added structured interview plan for frontend candidates focusing on HTML, CSS, JavaScript, TypeScript, and Vue. - Included starter files for each exercise and detailed README documentation for guidance. --- 08-vue3/01-create-app-and-ref/README.md | 23 ++ 08-vue3/01-create-app-and-ref/starter.html | 23 ++ 08-vue3/01-create-app-and-ref/starter.js | 20 ++ 08-vue3/02-reactive-and-computed/README.md | 23 ++ 08-vue3/02-reactive-and-computed/starter.html | 25 ++ 08-vue3/02-reactive-and-computed/starter.js | 26 +++ 08-vue3/03-watch-and-watch-effect/README.md | 23 ++ .../03-watch-and-watch-effect/starter.html | 25 ++ 08-vue3/03-watch-and-watch-effect/starter.js | 30 +++ .../04-lifecycle-and-template-ref/README.md | 23 ++ .../starter.html | 23 ++ .../04-lifecycle-and-template-ref/starter.js | 28 +++ 08-vue3/05-props-and-emits/README.md | 22 ++ 08-vue3/05-props-and-emits/starter.html | 28 +++ 08-vue3/05-props-and-emits/starter.js | 39 ++++ 08-vue3/06-slots-and-provide-inject/README.md | 22 ++ .../06-slots-and-provide-inject/starter.html | 26 +++ .../06-slots-and-provide-inject/starter.js | 27 +++ 08-vue3/07-composable-and-async/README.md | 26 +++ 08-vue3/07-composable-and-async/starter.html | 26 +++ 08-vue3/07-composable-and-async/starter.js | 38 +++ 08-vue3/08-final-dashboard/README.md | 30 +++ 08-vue3/08-final-dashboard/starter.html | 56 +++++ 08-vue3/08-final-dashboard/starter.js | 85 +++++++ 08-vue3/09-reactivity-helpers/README.md | 24 ++ 08-vue3/09-reactivity-helpers/starter.html | 26 +++ 08-vue3/09-reactivity-helpers/starter.js | 33 +++ .../README.md | 24 ++ .../starter.html | 29 +++ .../starter.js | 52 +++++ 08-vue3/11-before-hooks-and-expose/README.md | 24 ++ .../11-before-hooks-and-expose/starter.html | 25 ++ 08-vue3/11-before-hooks-and-expose/starter.js | 59 +++++ 08-vue3/12-built-in-components/README.md | 22 ++ 08-vue3/12-built-in-components/starter.html | 47 ++++ 08-vue3/12-built-in-components/starter.js | 24 ++ 08-vue3/13-script-setup-macros/README.md | 26 +++ 08-vue3/13-script-setup-macros/starter.vue | 38 +++ 08-vue3/README.md | 141 ++++++++++++ 09-interview-plan/README.md | 217 ++++++++++++++++++ README.md | 28 ++- 41 files changed, 1551 insertions(+), 5 deletions(-) create mode 100644 08-vue3/01-create-app-and-ref/README.md create mode 100644 08-vue3/01-create-app-and-ref/starter.html create mode 100644 08-vue3/01-create-app-and-ref/starter.js create mode 100644 08-vue3/02-reactive-and-computed/README.md create mode 100644 08-vue3/02-reactive-and-computed/starter.html create mode 100644 08-vue3/02-reactive-and-computed/starter.js create mode 100644 08-vue3/03-watch-and-watch-effect/README.md create mode 100644 08-vue3/03-watch-and-watch-effect/starter.html create mode 100644 08-vue3/03-watch-and-watch-effect/starter.js create mode 100644 08-vue3/04-lifecycle-and-template-ref/README.md create mode 100644 08-vue3/04-lifecycle-and-template-ref/starter.html create mode 100644 08-vue3/04-lifecycle-and-template-ref/starter.js create mode 100644 08-vue3/05-props-and-emits/README.md create mode 100644 08-vue3/05-props-and-emits/starter.html create mode 100644 08-vue3/05-props-and-emits/starter.js create mode 100644 08-vue3/06-slots-and-provide-inject/README.md create mode 100644 08-vue3/06-slots-and-provide-inject/starter.html create mode 100644 08-vue3/06-slots-and-provide-inject/starter.js create mode 100644 08-vue3/07-composable-and-async/README.md create mode 100644 08-vue3/07-composable-and-async/starter.html create mode 100644 08-vue3/07-composable-and-async/starter.js create mode 100644 08-vue3/08-final-dashboard/README.md create mode 100644 08-vue3/08-final-dashboard/starter.html create mode 100644 08-vue3/08-final-dashboard/starter.js create mode 100644 08-vue3/09-reactivity-helpers/README.md create mode 100644 08-vue3/09-reactivity-helpers/starter.html create mode 100644 08-vue3/09-reactivity-helpers/starter.js create mode 100644 08-vue3/10-next-tick-and-component-v-model/README.md create mode 100644 08-vue3/10-next-tick-and-component-v-model/starter.html create mode 100644 08-vue3/10-next-tick-and-component-v-model/starter.js create mode 100644 08-vue3/11-before-hooks-and-expose/README.md create mode 100644 08-vue3/11-before-hooks-and-expose/starter.html create mode 100644 08-vue3/11-before-hooks-and-expose/starter.js create mode 100644 08-vue3/12-built-in-components/README.md create mode 100644 08-vue3/12-built-in-components/starter.html create mode 100644 08-vue3/12-built-in-components/starter.js create mode 100644 08-vue3/13-script-setup-macros/README.md create mode 100644 08-vue3/13-script-setup-macros/starter.vue create mode 100644 08-vue3/README.md create mode 100644 09-interview-plan/README.md diff --git a/08-vue3/01-create-app-and-ref/README.md b/08-vue3/01-create-app-and-ref/README.md new file mode 100644 index 0000000..1fd02ec --- /dev/null +++ b/08-vue3/01-create-app-and-ref/README.md @@ -0,0 +1,23 @@ +# 练习 1:createApp、setup 和 ref + +## 目标 + +学会用 Vue3 的 `createApp` 和 `setup()` 启动页面,并用 `ref` 创建最基础的响应式数据。 + +## 你要练什么 + +- `createApp` +- `setup()` +- `ref` +- 模板插值 + +## 任务 + +- 显示页面标题和学习人数 +- 点击按钮后让人数加 1 +- 在控制台输出最新人数 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/01-create-app-and-ref/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/01-create-app-and-ref/starter.js) diff --git a/08-vue3/01-create-app-and-ref/starter.html b/08-vue3/01-create-app-and-ref/starter.html new file mode 100644 index 0000000..fb563d0 --- /dev/null +++ b/08-vue3/01-create-app-and-ref/starter.html @@ -0,0 +1,23 @@ + + + + + + createApp、setup 和 ref + + + +
+

{{ title }}

+

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

+ +
+ + + + + diff --git a/08-vue3/01-create-app-and-ref/starter.js b/08-vue3/01-create-app-and-ref/starter.js new file mode 100644 index 0000000..ac28b78 --- /dev/null +++ b/08-vue3/01-create-app-and-ref/starter.js @@ -0,0 +1,20 @@ +const { createApp, ref } = Vue; + +createApp({ + setup() { + const title = ref("Vue3 基础入门"); + const learnerCount = ref(16); + + function increaseLearner() { + // 任务: + // 1. learnerCount 加 1 + // 2. 在控制台输出最新人数 + } + + return { + title, + learnerCount, + increaseLearner, + }; + }, +}).mount("#app"); diff --git a/08-vue3/02-reactive-and-computed/README.md b/08-vue3/02-reactive-and-computed/README.md new file mode 100644 index 0000000..6793a81 --- /dev/null +++ b/08-vue3/02-reactive-and-computed/README.md @@ -0,0 +1,23 @@ +# 练习 2:reactive 和 computed + +## 目标 + +学会把一组相关数据放进 `reactive`,并用 `computed` 推导派生结果。 + +## 你要练什么 + +- `reactive` +- `computed` +- 响应式对象 +- 派生状态 + +## 任务 + +- 用 `reactive` 管理课程信息 +- 用 `computed` 计算进度文案 +- 点击按钮后让完成课时增加 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/02-reactive-and-computed/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/02-reactive-and-computed/starter.js) diff --git a/08-vue3/02-reactive-and-computed/starter.html b/08-vue3/02-reactive-and-computed/starter.html new file mode 100644 index 0000000..7f03c65 --- /dev/null +++ b/08-vue3/02-reactive-and-computed/starter.html @@ -0,0 +1,25 @@ + + + + + + reactive 和 computed + + + +
+

{{ course.title }}

+

总课时:{{ course.totalLessons }}

+

已完成:{{ course.finishedLessons }}

+

{{ progressText }}

+ +
+ + + + + diff --git a/08-vue3/02-reactive-and-computed/starter.js b/08-vue3/02-reactive-and-computed/starter.js new file mode 100644 index 0000000..201cff2 --- /dev/null +++ b/08-vue3/02-reactive-and-computed/starter.js @@ -0,0 +1,26 @@ +const { createApp, reactive, computed } = Vue; + +createApp({ + setup() { + const course = reactive({ + title: "Vue3 响应式基础", + totalLessons: 10, + finishedLessons: 3, + }); + + const progressText = computed(() => { + // 任务:返回类似 “当前已完成 3 / 10 节” + return ""; + }); + + function finishOneLesson() { + // 任务:在不超过总课时的前提下,finishedLessons 加 1 + } + + return { + course, + progressText, + finishOneLesson, + }; + }, +}).mount("#app"); diff --git a/08-vue3/03-watch-and-watch-effect/README.md b/08-vue3/03-watch-and-watch-effect/README.md new file mode 100644 index 0000000..e1f115a --- /dev/null +++ b/08-vue3/03-watch-and-watch-effect/README.md @@ -0,0 +1,23 @@ +# 练习 3:watch 和 watchEffect + +## 目标 + +学会区分“监听指定数据变化”和“自动收集依赖并执行副作用”。 + +## 你要练什么 + +- `watch` +- `watchEffect` +- 搜索关键字监听 +- 副作用日志 + +## 任务 + +- 输入关键字时,用 `watch` 输出变化日志 +- 用 `watchEffect` 输出当前筛选信息 +- 根据关键字过滤课程列表 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/03-watch-and-watch-effect/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/03-watch-and-watch-effect/starter.js) diff --git a/08-vue3/03-watch-and-watch-effect/starter.html b/08-vue3/03-watch-and-watch-effect/starter.html new file mode 100644 index 0000000..eccf182 --- /dev/null +++ b/08-vue3/03-watch-and-watch-effect/starter.html @@ -0,0 +1,25 @@ + + + + + + watch 和 watchEffect + + + +
+

课程搜索

+ + +
+ + + + + diff --git a/08-vue3/03-watch-and-watch-effect/starter.js b/08-vue3/03-watch-and-watch-effect/starter.js new file mode 100644 index 0000000..97006d9 --- /dev/null +++ b/08-vue3/03-watch-and-watch-effect/starter.js @@ -0,0 +1,30 @@ +const { createApp, ref, computed, watch, watchEffect } = Vue; + +createApp({ + setup() { + const keyword = ref(""); + const courses = ref([ + { id: 1, title: "ref 和 reactive" }, + { id: 2, title: "watch 和 watchEffect" }, + { id: 3, title: "组件通信" }, + ]); + + const filteredCourses = computed(() => { + // 任务:根据 keyword 过滤课程 + return courses.value; + }); + + watch(keyword, (newValue, oldValue) => { + // 任务:输出关键字变化日志 + }); + + watchEffect(() => { + // 任务:输出当前筛选后的数量 + }); + + return { + keyword, + filteredCourses, + }; + }, +}).mount("#app"); diff --git a/08-vue3/04-lifecycle-and-template-ref/README.md b/08-vue3/04-lifecycle-and-template-ref/README.md new file mode 100644 index 0000000..fb5f9b1 --- /dev/null +++ b/08-vue3/04-lifecycle-and-template-ref/README.md @@ -0,0 +1,23 @@ +# 练习 4:生命周期和模板 ref + +## 目标 + +学会在组合式 API 里使用生命周期钩子,并通过模板 `ref` 获取 DOM。 + +## 你要练什么 + +- `onMounted` +- `onUpdated` +- `onUnmounted` +- 模板 `ref` + +## 任务 + +- 页面挂载后自动聚焦输入框 +- 数据更新后输出日志 +- 页面卸载前清理定时器 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/04-lifecycle-and-template-ref/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/04-lifecycle-and-template-ref/starter.js) diff --git a/08-vue3/04-lifecycle-and-template-ref/starter.html b/08-vue3/04-lifecycle-and-template-ref/starter.html new file mode 100644 index 0000000..c2591d6 --- /dev/null +++ b/08-vue3/04-lifecycle-and-template-ref/starter.html @@ -0,0 +1,23 @@ + + + + + + 生命周期和模板 ref + + + +
+

生命周期练习

+ +

当前输入:{{ keyword }}

+
+ + + + + diff --git a/08-vue3/04-lifecycle-and-template-ref/starter.js b/08-vue3/04-lifecycle-and-template-ref/starter.js new file mode 100644 index 0000000..5171a84 --- /dev/null +++ b/08-vue3/04-lifecycle-and-template-ref/starter.js @@ -0,0 +1,28 @@ +const { createApp, ref, onMounted, onUpdated, onUnmounted } = Vue; + +createApp({ + setup() { + const keyword = ref(""); + const keywordInput = ref(null); + let timer = null; + + onMounted(() => { + // 任务: + // 1. 让输入框自动聚焦 + // 2. 建立一个定时器 + }); + + onUpdated(() => { + // 任务:输出 updated 日志 + }); + + onUnmounted(() => { + // 任务:清理定时器并输出销毁日志 + }); + + return { + keyword, + keywordInput, + }; + }, +}).mount("#app"); diff --git a/08-vue3/05-props-and-emits/README.md b/08-vue3/05-props-and-emits/README.md new file mode 100644 index 0000000..2ff91cc --- /dev/null +++ b/08-vue3/05-props-and-emits/README.md @@ -0,0 +1,22 @@ +# 练习 5:props 和 emit + +## 目标 + +学会在 Vue3 里写基础组件通信。 + +## 你要练什么 + +- `props` +- `emit` +- 组件拆分 + +## 任务 + +- 把课程项拆成子组件 +- 父组件传入课程对象 +- 子组件点击按钮后通知父组件切换完成状态 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/05-props-and-emits/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/05-props-and-emits/starter.js) diff --git a/08-vue3/05-props-and-emits/starter.html b/08-vue3/05-props-and-emits/starter.html new file mode 100644 index 0000000..451ce32 --- /dev/null +++ b/08-vue3/05-props-and-emits/starter.html @@ -0,0 +1,28 @@ + + + + + + props 和 emit + + + +
+ +
+ + + + + diff --git a/08-vue3/05-props-and-emits/starter.js b/08-vue3/05-props-and-emits/starter.js new file mode 100644 index 0000000..a784ac7 --- /dev/null +++ b/08-vue3/05-props-and-emits/starter.js @@ -0,0 +1,39 @@ +const { createApp } = Vue; + +createApp({ + components: { + CourseItem: { + props: { + course: { + type: Object, + required: true, + }, + }, + emits: ["toggle"], + template: ` +
+

{{ course.title }}

+

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

+ +
+ `, + }, + }, + setup() { + const courses = Vue.ref([ + { id: 1, title: "组合式 API", finished: true }, + { id: 2, title: "组件通信", finished: false }, + ]); + + function toggleCourse(courseId) { + // 任务:根据 courseId 切换对应课程的 finished + } + + return { + courses, + toggleCourse, + }; + }, +}).mount("#app"); diff --git a/08-vue3/06-slots-and-provide-inject/README.md b/08-vue3/06-slots-and-provide-inject/README.md new file mode 100644 index 0000000..8028d7a --- /dev/null +++ b/08-vue3/06-slots-and-provide-inject/README.md @@ -0,0 +1,22 @@ +# 练习 6:slot 与 provide / inject + +## 目标 + +学会更灵活地组织组件树中的内容和共享信息。 + +## 你要练什么 + +- `slot` +- `provide` +- `inject` + +## 任务 + +- 用插槽自定义卡片按钮文案 +- 父组件通过 `provide` 提供主题色 +- 子组件通过 `inject` 使用主题信息 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/06-slots-and-provide-inject/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/06-slots-and-provide-inject/starter.js) diff --git a/08-vue3/06-slots-and-provide-inject/starter.html b/08-vue3/06-slots-and-provide-inject/starter.html new file mode 100644 index 0000000..81faf59 --- /dev/null +++ b/08-vue3/06-slots-and-provide-inject/starter.html @@ -0,0 +1,26 @@ + + + + + + slot 与 provide/inject + + + +
+ + + +
+ + + + + diff --git a/08-vue3/06-slots-and-provide-inject/starter.js b/08-vue3/06-slots-and-provide-inject/starter.js new file mode 100644 index 0000000..e7017e8 --- /dev/null +++ b/08-vue3/06-slots-and-provide-inject/starter.js @@ -0,0 +1,27 @@ +const { createApp, provide, inject } = Vue; + +createApp({ + components: { + ThemeCard: { + setup() { + const themeColor = inject("themeColor"); + + return { + themeColor, + }; + }, + template: ` +
+

主题卡片

+ +
+ `, + }, + }, + setup() { + // 任务:通过 provide 提供 themeColor + return {}; + }, +}).mount("#app"); diff --git a/08-vue3/07-composable-and-async/README.md b/08-vue3/07-composable-and-async/README.md new file mode 100644 index 0000000..90e6bde --- /dev/null +++ b/08-vue3/07-composable-and-async/README.md @@ -0,0 +1,26 @@ +# 练习 7:composable 和异步状态 + +## 目标 + +学会把可复用逻辑抽成 composable,并管理 loading / error / data。 + +## 你要练什么 + +- composable +- `ref` +- 异步状态 +- `loading` +- `error` + +## 任务 + +- 把课程请求逻辑抽成 `useCourses` +- 页面加载时调用它 +- 显示 loading +- 请求成功后渲染列表 +- 请求失败时显示错误信息 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/07-composable-and-async/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/07-composable-and-async/starter.js) diff --git a/08-vue3/07-composable-and-async/starter.html b/08-vue3/07-composable-and-async/starter.html new file mode 100644 index 0000000..aed2353 --- /dev/null +++ b/08-vue3/07-composable-and-async/starter.html @@ -0,0 +1,26 @@ + + + + + + composable 和异步状态 + + + +
+

课程请求练习

+

数据加载中...

+

{{ error }}

+ +
+ + + + + diff --git a/08-vue3/07-composable-and-async/starter.js b/08-vue3/07-composable-and-async/starter.js new file mode 100644 index 0000000..6689306 --- /dev/null +++ b/08-vue3/07-composable-and-async/starter.js @@ -0,0 +1,38 @@ +const { createApp, ref, onMounted } = Vue; + +function useCourses() { + const courses = ref([]); + const loading = ref(true); + const error = ref(""); + + async function loadCourses() { + // 任务: + // 1. 模拟异步请求 + // 2. 成功时给 courses 赋值 + // 3. 失败时给 error 赋值 + // 4. 最后把 loading 设为 false + } + + return { + courses, + loading, + error, + loadCourses, + }; +} + +createApp({ + setup() { + const { courses, loading, error, loadCourses } = useCourses(); + + onMounted(() => { + // 任务:页面挂载后调用 loadCourses + }); + + return { + courses, + loading, + error, + }; + }, +}).mount("#app"); diff --git a/08-vue3/08-final-dashboard/README.md b/08-vue3/08-final-dashboard/README.md new file mode 100644 index 0000000..581ed9c --- /dev/null +++ b/08-vue3/08-final-dashboard/README.md @@ -0,0 +1,30 @@ +# 练习 8:Vue3 综合小面板 + +## 目标 + +把 Vue3 组合式 API 的主线能力串起来,完成一个小型课程面板。 + +## 你要练什么 + +- `ref` +- `reactive` +- `computed` +- `watch` +- 组件通信 +- composable +- 模板 `ref` + +## 任务 + +- 做一个课程搜索和筛选面板 +- 用 `computed` 计算筛选结果和统计数据 +- 用 `watch` 输出搜索关键字变化 +- 用子组件渲染课程卡片 +- 点击按钮切换课程完成状态 +- 点击按钮聚焦搜索框 +- 抽一个 composable 管理课程数据 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/08-final-dashboard/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/08-final-dashboard/starter.js) diff --git a/08-vue3/08-final-dashboard/starter.html b/08-vue3/08-final-dashboard/starter.html new file mode 100644 index 0000000..6035e8b --- /dev/null +++ b/08-vue3/08-final-dashboard/starter.html @@ -0,0 +1,56 @@ + + + + + + Vue3 综合小面板 + + + +
+
+

Vue3 综合课程面板

+
+ + + +
+
+ +
+
暂无匹配结果
+
+ +
+
+ +
+

总课程数:{{ totalCount }}

+

已完成数:{{ finishedCount }}

+

当前筛选数:{{ visibleCount }}

+
+
+ + + + + diff --git a/08-vue3/08-final-dashboard/starter.js b/08-vue3/08-final-dashboard/starter.js new file mode 100644 index 0000000..9940677 --- /dev/null +++ b/08-vue3/08-final-dashboard/starter.js @@ -0,0 +1,85 @@ +const { createApp, ref, computed, watch } = Vue; + +function useCourses() { + const courses = ref([ + { id: 1, title: "setup 和 ref", finished: true }, + { id: 2, title: "reactive 和 computed", finished: false }, + { id: 3, title: "组件通信", finished: false }, + { id: 4, title: "composable 实战", finished: true }, + ]); + + function toggleCourse(courseId) { + // 任务:根据 courseId 切换课程完成状态 + } + + return { + courses, + toggleCourse, + }; +} + +createApp({ + components: { + CourseItem: { + props: { + course: { + type: Object, + required: true, + }, + }, + emits: ["toggle"], + template: ` +
+

{{ course.title }}

+

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

+ +
+ `, + }, + }, + setup() { + const keyword = ref(""); + const statusFilter = ref("all"); + const searchInput = ref(null); + const { courses, toggleCourse } = useCourses(); + + const filteredCourses = computed(() => { + // 任务:同时按关键字和状态筛选课程 + return courses.value; + }); + + const totalCount = computed(() => { + return courses.value.length; + }); + + const finishedCount = computed(() => { + // 任务:返回已完成数量 + return 0; + }); + + const visibleCount = computed(() => { + // 任务:返回当前筛选后的数量 + return 0; + }); + + watch(keyword, (newValue) => { + // 任务:输出关键字变化 + }); + + function focusSearch() { + // 任务:通过模板 ref 聚焦输入框 + } + + return { + keyword, + statusFilter, + searchInput, + filteredCourses, + totalCount, + finishedCount, + visibleCount, + toggleCourse, + focusSearch, + }; + }, +}).mount("#app"); diff --git a/08-vue3/09-reactivity-helpers/README.md b/08-vue3/09-reactivity-helpers/README.md new file mode 100644 index 0000000..a5d7530 --- /dev/null +++ b/08-vue3/09-reactivity-helpers/README.md @@ -0,0 +1,24 @@ +# 练习 9:toRef、toRefs 和 readonly + +## 目标 + +学会把响应式对象里的字段拆出来继续保持响应式,并理解只读数据的使用场景。 + +## 你要练什么 + +- `toRef` +- `toRefs` +- `readonly` + +## 任务 + +- 用 `reactive` 管理学习者信息 +- 用 `toRef` 单独取出 `name` +- 用 `toRefs` 拆出其余字段 +- 用 `readonly` 包一层设置项 +- 点击按钮更新学习者信息并观察页面变化 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/09-reactivity-helpers/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/09-reactivity-helpers/starter.js) diff --git a/08-vue3/09-reactivity-helpers/starter.html b/08-vue3/09-reactivity-helpers/starter.html new file mode 100644 index 0000000..0973182 --- /dev/null +++ b/08-vue3/09-reactivity-helpers/starter.html @@ -0,0 +1,26 @@ + + + + + + toRef、toRefs 和 readonly + + + +
+

{{ name }}

+

当前阶段:{{ stage }}

+

学习天数:{{ studyDays }}

+

主题模式:{{ settings.theme }}

+ +
+ + + + + diff --git a/08-vue3/09-reactivity-helpers/starter.js b/08-vue3/09-reactivity-helpers/starter.js new file mode 100644 index 0000000..13dab90 --- /dev/null +++ b/08-vue3/09-reactivity-helpers/starter.js @@ -0,0 +1,33 @@ +const { createApp, reactive, toRef, toRefs, readonly } = Vue; + +createApp({ + setup() { + const profile = reactive({ + name: "林晨", + stage: "Vue3 入门", + studyDays: 12, + }); + + const name = toRef(profile, "name"); + const { stage, studyDays } = toRefs(profile); + const settings = readonly({ + theme: "light", + }); + + function updateProfile() { + // 任务: + // 1. 更新 name.value + // 2. 更新 stage.value + // 3. 让 studyDays.value + 1 + // 4. 不要直接修改 settings.theme + } + + return { + name, + stage, + studyDays, + settings, + updateProfile, + }; + }, +}).mount("#app"); diff --git a/08-vue3/10-next-tick-and-component-v-model/README.md b/08-vue3/10-next-tick-and-component-v-model/README.md new file mode 100644 index 0000000..91d2bed --- /dev/null +++ b/08-vue3/10-next-tick-and-component-v-model/README.md @@ -0,0 +1,24 @@ +# 练习 10:nextTick 和组件 v-model + +## 目标 + +学会在 DOM 更新完成后执行逻辑,并理解 Vue3 组件 `v-model` 的通信约定。 + +## 你要练什么 + +- `nextTick` +- 组件 `v-model` +- `modelValue` +- `update:modelValue` + +## 任务 + +- 封装一个搜索输入子组件 +- 父组件通过 `v-model` 绑定关键字 +- 点击“展开搜索区”后,等 DOM 更新完成再聚焦输入框 +- 在控制台输出关键字变化 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/10-next-tick-and-component-v-model/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/10-next-tick-and-component-v-model/starter.js) diff --git a/08-vue3/10-next-tick-and-component-v-model/starter.html b/08-vue3/10-next-tick-and-component-v-model/starter.html new file mode 100644 index 0000000..3d07783 --- /dev/null +++ b/08-vue3/10-next-tick-and-component-v-model/starter.html @@ -0,0 +1,29 @@ + + + + + + nextTick 和组件 v-model + + + +
+ + +
+ +
+ +

当前关键字:{{ keyword }}

+
+ + + + + diff --git a/08-vue3/10-next-tick-and-component-v-model/starter.js b/08-vue3/10-next-tick-and-component-v-model/starter.js new file mode 100644 index 0000000..f82e007 --- /dev/null +++ b/08-vue3/10-next-tick-and-component-v-model/starter.js @@ -0,0 +1,52 @@ +const { createApp, ref, nextTick, watch } = Vue; + +createApp({ + components: { + SearchInput: { + props: { + modelValue: { + type: String, + default: "", + }, + }, + emits: ["update:modelValue"], + template: ` + + `, + methods: { + focus() { + this.$refs.inputEl.focus(); + }, + }, + }, + }, + setup() { + const showSearch = ref(false); + const keyword = ref(""); + const searchBox = ref(null); + + watch(keyword, (newValue) => { + // 任务:在控制台输出关键字变化 + }); + + async function toggleSearch() { + // 任务: + // 1. 切换 showSearch.value + // 2. 如果展开了,await nextTick() + // 3. 通过 searchBox.value.focus() 聚焦输入框 + } + + return { + showSearch, + keyword, + searchBox, + toggleSearch, + }; + }, +}).mount("#app"); diff --git a/08-vue3/11-before-hooks-and-expose/README.md b/08-vue3/11-before-hooks-and-expose/README.md new file mode 100644 index 0000000..bf6fc9e --- /dev/null +++ b/08-vue3/11-before-hooks-and-expose/README.md @@ -0,0 +1,24 @@ +# 练习 11:before 系列生命周期和 expose + +## 目标 + +学会在组合式 API 中使用 before 系列生命周期,并理解子组件如何有选择地暴露能力给父组件。 + +## 你要练什么 + +- `onBeforeMount` +- `onBeforeUpdate` +- `onBeforeUnmount` +- `expose` + +## 任务 + +- 在不同生命周期里输出日志 +- 父组件通过模板 `ref` 获取子组件实例 +- 子组件通过 `expose` 暴露一个 `focusInput` 方法 +- 父组件点击按钮后调用这个暴露出来的方法 + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/11-before-hooks-and-expose/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/11-before-hooks-and-expose/starter.js) diff --git a/08-vue3/11-before-hooks-and-expose/starter.html b/08-vue3/11-before-hooks-and-expose/starter.html new file mode 100644 index 0000000..2544163 --- /dev/null +++ b/08-vue3/11-before-hooks-and-expose/starter.html @@ -0,0 +1,25 @@ + + + + + + before 系列生命周期和 expose + + + +
+ + + +
+ + + + + diff --git a/08-vue3/11-before-hooks-and-expose/starter.js b/08-vue3/11-before-hooks-and-expose/starter.js new file mode 100644 index 0000000..f91c182 --- /dev/null +++ b/08-vue3/11-before-hooks-and-expose/starter.js @@ -0,0 +1,59 @@ +const { + createApp, + ref, + onBeforeMount, + onBeforeUpdate, + onBeforeUnmount, +} = Vue; + +createApp({ + components: { + ChildPanel: { + template: ` +
+

我是子组件

+ +
+ `, + setup(props, { expose }) { + const inputEl = ref(null); + + onBeforeMount(() => { + // 任务:输出 beforeMount 日志 + }); + + onBeforeUpdate(() => { + // 任务:输出 beforeUpdate 日志 + }); + + onBeforeUnmount(() => { + // 任务:输出 beforeUnmount 日志 + }); + + function focusInput() { + // 任务:聚焦 inputEl + } + + // 任务:通过 expose 暴露 focusInput + + return { + inputEl, + }; + }, + }, + }, + setup() { + const showChild = ref(true); + const childPanel = ref(null); + + function focusChildInput() { + // 任务:调用 childPanel.value 暴露出来的方法 + } + + return { + showChild, + childPanel, + focusChildInput, + }; + }, +}).mount("#app"); diff --git a/08-vue3/12-built-in-components/README.md b/08-vue3/12-built-in-components/README.md new file mode 100644 index 0000000..6a9e1b1 --- /dev/null +++ b/08-vue3/12-built-in-components/README.md @@ -0,0 +1,22 @@ +# 练习 12:Teleport、Suspense 和 Transition + +## 目标 + +认识 Vue3 常见内置组件在实际页面里的使用方式。 + +## 你要练什么 + +- `Teleport` +- `Suspense` +- `Transition` + +## 任务 + +- 用 `Teleport` 把弹层渲染到 `body` +- 用 `Transition` 给弹层或提示做显隐动画 +- 用 `Suspense` 包裹一个异步组件,并显示 fallback + +## 文件 + +- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/12-built-in-components/starter.html) +- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/12-built-in-components/starter.js) diff --git a/08-vue3/12-built-in-components/starter.html b/08-vue3/12-built-in-components/starter.html new file mode 100644 index 0000000..2bc1b76 --- /dev/null +++ b/08-vue3/12-built-in-components/starter.html @@ -0,0 +1,47 @@ + + + + + + Teleport、Suspense 和 Transition + + + +
+

内置组件练习

+ + + + + + + + + + + + +
+ + + + + diff --git a/08-vue3/12-built-in-components/starter.js b/08-vue3/12-built-in-components/starter.js new file mode 100644 index 0000000..bee91ce --- /dev/null +++ b/08-vue3/12-built-in-components/starter.js @@ -0,0 +1,24 @@ +const { createApp, ref } = Vue; + +createApp({ + components: { + AsyncInfo: { + async setup() { + // 任务: + // 1. 模拟等待 + // 2. 返回需要在模板中展示的数据 + return {}; + }, + template: ` +

这里会展示异步组件内容。

+ `, + }, + }, + setup() { + const showModal = ref(false); + + return { + showModal, + }; + }, +}).mount("#app"); diff --git a/08-vue3/13-script-setup-macros/README.md b/08-vue3/13-script-setup-macros/README.md new file mode 100644 index 0000000..88fb9bc --- /dev/null +++ b/08-vue3/13-script-setup-macros/README.md @@ -0,0 +1,26 @@ +# 练习 13:script setup、defineProps、defineEmits、defineExpose + +## 目标 + +补上 Vue3 在工程化单文件组件里的核心宏语法。 + +## 你要练什么 + +- ` + + diff --git a/08-vue3/README.md b/08-vue3/README.md new file mode 100644 index 0000000..b1f0851 --- /dev/null +++ b/08-vue3/README.md @@ -0,0 +1,141 @@ +# Vue3(组合式 API + 响应式原理) + +你的学习文档里这一章的定位是 `Vue3(组合式API + 响应式原理)`。这一章我按这个方向来拆,不再重复 Vue2 的 Options API 主线,而是重点转到 Vue3 的组合式写法和响应式思维。 + +## 学完后你应该掌握 + +- Vue3 和 Vue2 的核心差异 +- `createApp` +- `setup()` +- `ref` +- `reactive` +- `toRef` +- `toRefs` +- `readonly` +- `computed` +- `watch` +- `watchEffect` +- `nextTick` +- 组合式 API 生命周期 +- 模板 `ref` +- `props` 和 `emit` +- 组件 `v-model` +- `slot` +- `provide` / `inject` +- `expose` +- composable 的基本抽离方式 +- `script setup` +- `defineProps` +- `defineEmits` +- `defineExpose` +- `Teleport` +- `Suspense` +- `Transition` +- 如何用 Vue3 写一个小型管理面板 + +## 这一章在解决什么 + +Vue2 更强调“选项式组织”。 + +Vue3 这一章要解决的是: + +- 逻辑如何按功能组织,而不是按选项分散 +- 响应式数据如何在 `setup()` 里组合 +- 一段可复用逻辑如何抽成 composable +- 组件之间如何在组合式 API 下继续通信 + +## 全部知识点清单 + +### 基础入口 + +- `createApp` +- `setup()` +- `return` + +### 响应式核心 + +- `ref` +- `reactive` +- `toRef` +- `toRefs` +- `readonly` +- `computed` +- `watch` +- `watchEffect` +- `nextTick` + +### 生命周期与 DOM + +- `onBeforeMount` +- `onMounted` +- `onBeforeUpdate` +- `onUpdated` +- `onBeforeUnmount` +- `onUnmounted` +- 模板 `ref` +- `expose` + +### 组件通信 + +- `props` +- `emit` +- 组件 `v-model` +- `slot` +- `provide` +- `inject` + +### 逻辑复用 + +- composable +- 异步状态管理 +- `loading` +- `error` + +### 工程化语法与内置组件 + +- `script setup` +- `defineProps` +- `defineEmits` +- `defineExpose` +- `Teleport` +- `Suspense` +- `Transition` + +## 学习顺序 + +1. `createApp`、`setup()` 和 `ref` +2. `reactive` 和 `computed` +3. `watch` 和 `watchEffect` +4. 生命周期和模板 `ref` +5. `props` 和 `emit` +6. `slot` 与 `provide` / `inject` +7. composable 与异步状态 +8. `toRefs`、`readonly` 等响应式辅助工具 +9. `nextTick` 和组件 `v-model` +10. before 系列生命周期与 `expose` +11. `Teleport`、`Suspense`、`Transition` +12. `script setup` 宏 +13. 综合小页面 + +## 练习目录 + +- [01-create-app-and-ref/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/01-create-app-and-ref/README.md) +- [02-reactive-and-computed/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/02-reactive-and-computed/README.md) +- [03-watch-and-watch-effect/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/03-watch-and-watch-effect/README.md) +- [04-lifecycle-and-template-ref/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/04-lifecycle-and-template-ref/README.md) +- [05-props-and-emits/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/05-props-and-emits/README.md) +- [06-slots-and-provide-inject/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/06-slots-and-provide-inject/README.md) +- [07-composable-and-async/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/07-composable-and-async/README.md) +- [08-final-dashboard/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/08-final-dashboard/README.md) +- [09-reactivity-helpers/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/09-reactivity-helpers/README.md) +- [10-next-tick-and-component-v-model/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/10-next-tick-and-component-v-model/README.md) +- [11-before-hooks-and-expose/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/11-before-hooks-and-expose/README.md) +- [12-built-in-components/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/12-built-in-components/README.md) +- [13-script-setup-macros/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/13-script-setup-macros/README.md) + +## 说明 + +- 这一章只提供 `starter`,不提供 `answer` +- 为了降低门槛,练习使用浏览器 CDN 版本的 Vue3 +- `13-script-setup-macros` 是 SFC 语法练习,文件是 `.vue` starter,不是直接双击运行的 HTML +- 如果后面你要把这一章升级成 `Vite + Vue3`,我可以再继续补工程化版本 diff --git a/09-interview-plan/README.md b/09-interview-plan/README.md new file mode 100644 index 0000000..10a8927 --- /dev/null +++ b/09-interview-plan/README.md @@ -0,0 +1,217 @@ +# 前端面试官 Prompt + +你现在扮演一位有经验的前端面试官,负责基于这个学习仓库的内容,对候选人进行系统化面试训练。 + +## 你的任务 + +围绕下面 8 个知识单元出题、追问、点评,并帮助候选人把“会写”升级成“会讲”: + +- HTML +- CSS +- JavaScript Core +- DOM + 事件 + 异步 +- ES6+ +- TypeScript +- Vue2 +- Vue3 + +## 你的工作方式 + +### 1. 按阶段出题 + +请按下面 4 个阶段组织面试题: + +#### 第一阶段:结构与样式 + +- HTML +- CSS + +目标: + +- 检查候选人是否能把页面拆成结构 +- 检查候选人是否能解释常见布局方案 + +#### 第二阶段:JavaScript 主线 + +- JavaScript Core +- DOM + 事件 + 异步 +- ES6+ + +目标: + +- 检查候选人是否能说清基础语法、作用域、闭包、`this` +- 检查候选人是否能说清事件流、异步、模块化 + +#### 第三阶段:类型与 Vue2 + +- TypeScript +- Vue2 + +目标: + +- 检查候选人是否能解释类型系统的价值 +- 检查候选人是否能说清 Vue2 的数据驱动和组件通信 + +#### 第四阶段:Vue3 与综合表达 + +- Vue3 + +目标: + +- 检查候选人是否能说清组合式 API、响应式、composable 和工程化语法 + +### 2. 每次只出一个单元 + +每次面试时: + +1. 先让我选择一个单元 +2. 再连续提出 5 到 8 道问题 +3. 每题都允许我回答 +4. 你根据我的回答继续追问 + +### 3. 每道题都按这个顺序处理 + +对每道题,请按下面流程进行: + +1. 先提出问题 +2. 等我回答 +3. 判断回答是否完整 +4. 如果不完整,继续追问 +5. 最后给出点评 + +## 你的提问标准 + +每道题尽量围绕这 4 层来设计: + +1. 定义是什么 +2. 使用场景是什么 +3. 常见坑点是什么 +4. 能不能举一个小例子 + +## 题目池 + +### HTML + +- 什么是语义化标签?为什么不要只用 `div`? +- 一个完整 HTML 文档的基本骨架包含哪些部分? +- 块级元素和行内元素的区别是什么? +- `section`、`article`、`aside` 分别适合什么场景? +- `ul` / `ol` / `li` 的嵌套规则是什么? +- `form`、`label`、`input` 的正确关系是什么? +- `alt`、`href`、`src`、`name` 分别有什么作用? +- 为什么说 HTML 更像页面骨架而不是样式代码? + +### CSS + +- 盒模型由哪些部分组成? +- `margin` 和 `padding` 的区别是什么? +- `display: block / inline / inline-block / flex / grid` 的常见差异是什么? +- Flex 最常用的几个属性分别解决什么问题? +- Grid 适合什么场景?和 Flex 的区别是什么? +- `position: relative / absolute / fixed / sticky` 分别怎么理解? +- 什么是文档流?脱离文档流会带来什么影响? +- 如何做水平垂直居中? + +### JavaScript Core + +- `var`、`let`、`const` 的区别是什么? +- JavaScript 常见数据类型有哪些? +- `undefined` 和 `null` 的区别是什么? +- `if / else` 和 `switch` 各适合什么场景? +- `for` 和 `while` 的区别是什么? +- 什么是函数?参数和返回值怎么理解? +- 数组和对象分别适合存什么数据? +- 什么是作用域?什么是闭包? +- `this` 在普通函数、对象方法、箭头函数里的区别是什么? +- 值传递和引用传递怎么理解? + +### DOM + 事件 + 异步 + +- 如何选中页面元素? +- `textContent`、`innerHTML`、`classList`、`style` 有什么常见用途? +- 如何创建、插入、删除节点? +- `addEventListener` 的作用是什么? +- 事件冒泡是什么?事件委托为什么有用? +- `preventDefault()` 和 `stopPropagation()` 分别解决什么问题? +- `setTimeout` 为什么体现异步? +- Promise 和 `async/await` 在页面交互里怎么配合? + +### ES6+ + +- 模板字符串和字符串拼接相比有什么优势? +- 解构赋值的典型使用场景是什么? +- 展开运算符和剩余参数分别做什么? +- 箭头函数和普通函数的差异有哪些? +- 为什么箭头函数里的 `this` 容易被问到? +- `import / export` 的基本写法是什么? +- `fetch()` 和 `res.json()` 的关系是什么? +- Promise 和 `async/await` 的关系是什么? + +### TypeScript + +- TypeScript 和 JavaScript 的核心差异是什么? +- 为什么说 TypeScript 的价值主要发生在运行前? +- 基本类型、数组类型、函数类型怎么写? +- `interface` 适合解决什么问题? +- 泛型 `` 的核心价值是什么? +- 联合类型和可选属性的常见场景是什么? +- TypeScript 报错时应该先看什么? +- 为什么说类型要服务于业务? + +### Vue2 + +- Vue2 为什么说是数据驱动视图? +- `new Vue()` 里最常见的几个选项是什么? +- `v-bind`、`v-on`、`v-model` 分别做什么? +- `v-if` 和 `v-show` 的区别是什么? +- `computed` 和 `watch` 的区别是什么? +- Vue2 的生命周期最常问哪几个? +- 父子组件怎么通过 `props` 和 `$emit` 通信? +- `slot` 和 `ref` 分别适合什么场景? + +### Vue3 + +- Vue3 和 Vue2 最大的思维差异是什么? +- `setup()` 为什么是组合式 API 的入口? +- `ref` 和 `reactive` 的区别是什么? +- `watch` 和 `watchEffect` 的区别是什么? +- `toRef`、`toRefs`、`readonly` 解决什么问题? +- `nextTick` 适合什么场景? +- Vue3 组件 `v-model` 的底层约定是什么? +- `provide / inject` 适合什么场景? +- 什么是 composable? +- `script setup`、`defineProps`、`defineEmits` 有什么作用? +- `Teleport`、`Suspense`、`Transition` 分别用来解决什么问题? + +## 回答评估标准 + +当我回答后,请从这 4 个维度给出判断: + +- 是否说对定义 +- 是否说出使用场景 +- 是否提到常见坑点 +- 是否给出例子或代码思路 + +如果回答不完整,请继续追问,不要立刻公布标准答案。 + +## 点评格式 + +每道题点评时请使用这个格式: + +```md +问题:... +评价:回答完整 / 基本正确 / 不完整 / 有明显错误 +缺失点:... +标准表达:... +追问题:... +``` + +## 启动方式 + +当我说“开始面试”时,请先问我: + +1. 你想刷哪个单元? +2. 你想要偏基础、偏中等,还是偏追问型? +3. 你想一次刷 5 题还是 8 题? + +然后直接开始扮演面试官,不要再解释规则。 diff --git a/README.md b/README.md index 4fba497..6c21ac9 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ - `05-es6-plus`:预留给 ES6+(现代 JS) - `06-typescript`:预留给 TypeScript - `07-vue2`:预留给 Vue2 +- `08-vue3`:预留给 Vue3 +- `09-interview-plan`:前端面试题计划 ## 当前可学内容 @@ -64,7 +66,21 @@ - `starter.html` / `starter.js` 起始代码 - `answer.html` / `answer.js` 参考答案 -前七部分现在都已经补充到“核心主线 + 常见细分知识点”。 +现在也已经整理好 `08-vue3`,里面包含: + +- Vue3(组合式 API + 响应式原理)讲义 +- 分阶段练习 +- `starter.html` / `starter.js` 起始代码 +- 当前按你的要求不提供 `answer` + +现在也已经整理好 `09-interview-plan`,里面包含: + +- 基于 01-08 各单元抽取的核心面试题 +- 分阶段复习顺序 +- 刷题执行方式 +- 复盘目标 + +前九部分现在都已经补充到“学习主线 + 复习计划 + 面试题整理”。 ## 使用方式 @@ -75,9 +91,11 @@ 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. 再阅读 [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` +8. 再阅读 [08-vue3/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/README.md) +9. 再阅读 [09-interview-plan/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/09-interview-plan/README.md) +10. 按顺序完成每个练习目录 +11. 先写 `starter.html`、`starter.css`、`starter.js` 或 `starter.ts` +12. 写完后再对照答案文件 +13. 学 `06-typescript` 时,也可以进入 [06-typescript](/Users/lijiaqing/home/wwwroot/front-end-example/06-typescript) 后执行 `npm install` 和 `npm run dev` 如果你后面要继续学其他知识点,我可以按同样结构继续给你补更多工程化目录。