[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"menus-public":3,"blog-10030":83,"blog-10030-comments":92},[4,15,43,66],{"id":5,"title":6,"link":7,"icon":8,"target":9,"sort_order":10,"is_active":11,"parent_id":12,"created_at":13,"children":14},10001,"首页","\u002F","","_blank",0,true,null,"2026-04-04T12:03:45",[],{"id":16,"title":17,"link":18,"icon":8,"target":19,"sort_order":20,"is_active":11,"parent_id":12,"created_at":21,"children":22},10038,"墨言博客","\u002Fblog","_self",1,"2026-10-30T23:08:14",[23,29,36],{"id":24,"title":25,"link":26,"icon":8,"target":19,"sort_order":20,"is_active":11,"parent_id":16,"created_at":27,"children":28},10039,"技术前沿","\u002Fblog?category=10001","2026-10-30T23:10:46",[],{"id":30,"title":31,"link":32,"icon":8,"target":19,"sort_order":33,"is_active":11,"parent_id":16,"created_at":34,"children":35},10042,"生活随笔","\u002Fblog?category=20007",2,"2026-10-30T23:55:19",[],{"id":37,"title":38,"link":39,"icon":8,"target":19,"sort_order":40,"is_active":11,"parent_id":16,"created_at":41,"children":42},10043,"旅游日记","\u002Fblog?category=20006",3,"2026-10-31T00:00:11",[],{"id":33,"title":44,"link":45,"icon":8,"target":19,"sort_order":33,"is_active":11,"parent_id":12,"created_at":46,"children":47},"新闻中心","\u002Fnews","2026-10-24T19:09:40",[48,54,60],{"id":49,"title":50,"link":51,"icon":8,"target":19,"sort_order":20,"is_active":11,"parent_id":33,"created_at":52,"children":53},10046,"产品动态","\u002Fnews?category=20010","2026-10-31T00:17:38",[],{"id":55,"title":56,"link":57,"icon":8,"target":19,"sort_order":33,"is_active":11,"parent_id":33,"created_at":58,"children":59},10047,"新闻热点","\u002Fnews?category=20009","2026-10-31T00:17:55",[],{"id":61,"title":62,"link":63,"icon":8,"target":19,"sort_order":40,"is_active":11,"parent_id":33,"created_at":64,"children":65},10045,"公司动态","\u002Fnews?category=20008","2026-10-31T00:17:11",[],{"id":67,"title":68,"link":69,"icon":8,"target":19,"sort_order":67,"is_active":11,"parent_id":12,"created_at":46,"children":70},4,"精彩视频","\u002Fvideo",[71,77],{"id":72,"title":73,"link":74,"icon":8,"target":19,"sort_order":10,"is_active":11,"parent_id":67,"created_at":75,"children":76},13,"活动记录","\u002Fvideo?category=6","2026-10-25T21:04:39",[],{"id":78,"title":79,"link":80,"icon":8,"target":19,"sort_order":20,"is_active":11,"parent_id":67,"created_at":81,"children":82},11,"产品展示","\u002Fvideo?category=5","2026-10-25T21:02:59",[],{"id":84,"category_id":5,"title":85,"author":8,"summary":86,"cover":87,"content":88,"tags":8,"seo_title":8,"seo_description":8,"seo_keywords":8,"og_image":8,"views":89,"comment_count":10,"is_top":90,"is_published":11,"created_at":91},10030,"Vue 3 快速入门教程","Vue 3 是渐进式 JavaScript 框架，核心只关注视图层，但可以逐步引入路由、状态管理、构建工具等。","\u002Fstatic\u002Fuploads\u002Fblogs\u002F1792071703565-22193834.png","# Vue 3 快速入门教程\n## 1. Vue 3 简介\n\nVue 3 是渐进式 JavaScript 框架，核心只关注视图层，但可以逐步引入路由、状态管理、构建工具等。相比 Vue 2，Vue 3 的主要变化包括：\n\n- 性能更好：Proxy 实现的响应式系统，编译优化\n- 组合式 API（Composition API）：更灵活的代码组织方式\n- TypeScript 支持更友好\n- 更小的包体积\n\nVue 3 应用的核心组成包括：响应式状态、模板 Template、组件 Component、生命周期钩子，以及虚拟 DOM 与 Diff 更新机制。\n\n## 2. 创建项目\n\n官方推荐用 Vite 创建 Vue 3 项目：\n\n```bash\nnpm create vue@latest my-vue-app\ncd my-vue-app\nnpm install\nnpm run dev\n```\n\n创建时会询问是否添加 TypeScript、Router、Pinia、ESLint 等。新手可以先只选 Vue Router，其他保持默认。\n\n项目结构：\n\n```\nmy-vue-app\u002F\n├── public\u002F\n├── src\u002F\n│   ├── assets\u002F\n│   ├── components\u002F\n│   ├── App.vue          # 根组件\n│   └── main.js            # 入口文件\n├── index.html\n├── package.json\n└── vite.config.js\n```\n\n`main.js` 内容：\n\n```javascript\nimport { createApp } from 'vue'\nimport App from '.\u002FApp.vue'\n\ncreateApp(App).mount('#app')\n```\n\n## 3. 单文件组件（SFC）\n\nVue 组件通常写在 `.vue` 文件中，一个文件包含三种代码块：\n\n```\n\u003Cscript setup>\nimport { ref } from 'vue'\n\nconst count = ref(0)\n\nfunction increment() {\n  count.value++\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton @click=\"increment\">\n    点击了 {{ count }} 次\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\nbutton {\n  color: #42b883;\n}\n\u003C\u002Fstyle>\n```\n\n- `\u003Cscript setup>` ：组合式 API 的推荐写法\n- `\u003Ctemplate>` ：HTML 模板\n- `\u003Cstyle scoped>` ：样式只作用于当前组件\n\n## 4. 模板语法\n\n### 文本插值\n\n```\n\u003Ctemplate>\n  \u003Cp>{{ message }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n```\n\n### 原始 HTML\n\n```\n\u003Ctemplate>\n  \u003Cdiv v-html=\"rawHtml\">\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n### 属性绑定\n\n```\n\u003Ctemplate>\n  \u003Cimg :src=\"imageUrl\" :alt=\"description\">\n  \u003Cbutton :disabled=\"isDisabled\">禁用\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n```\n\n`:` 是 `v-bind:` 的简写。\n\n### 事件监听\n\n```\n\u003Ctemplate>\n  \u003Cbutton @click=\"handleClick\">点击\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n```\n\n`@` 是 `v-on:` 的简写。\n\n### 条件渲染\n\n```\n\u003Ctemplate>\n  \u003Cp v-if=\"isShow\">显示\u003C\u002Fp>\n  \u003Cp v-else>隐藏\u003C\u002Fp>\n\u003C\u002Ftemplate>\n```\n\n### 列表渲染\n\n```\n\u003Ctemplate>\n  \u003Cul>\n    \u003Cli v-for=\"item in list\" :key=\"item.id\">\n      {{ item.name }}\n    \u003C\u002Fli>\n  \u003C\u002Ful>\n\u003C\u002Ftemplate>\n```\n\n> ⚠️ 一定要写 `:key` ，帮助 Vue 追踪每个节点。\n\n## 5. 响应式基础\n\nVue 3 用 `ref` 和 `reactive` 创建响应式数据。\n\n### ref\n\n用于基本类型和对象：\n\n```\n\u003Cscript setup>\nimport { ref } from 'vue'\n\nconst count = ref(0)\nconst user = ref({ name: '张三' })\n\nfunction add() {\n  count.value++           \u002F\u002F 访问\u002F修改需要用 .value\n  user.value.name = '李四'\n}\n\u003C\u002Fscript>\n```\n\n在 `\u003Ctemplate>` 中自动解包，直接写 `{{ count }}` 。\n\n### reactive\n\n只用于对象：\n\n```\n\u003Cscript setup>\nimport { reactive } from 'vue'\n\nconst state = reactive({\n  count: 0,\n  user: { name: '张三' }\n})\n\nfunction add() {\n  state.count++           \u002F\u002F 不需要 .value\n  state.user.name = '李四'\n}\n\u003C\u002Fscript>\n```\n\n### 选择建议\n\n- 基本类型：用 `ref`\n- 复杂对象：用 `reactive` 或 `ref`\n- 需要整体替换对象时：用 `ref` 更方便\n\n## 6. 计算属性与侦听器\n\n### computed\n\n基于响应式数据派生值，有缓存：\n\n```\n\u003Cscript setup>\nimport { ref, computed } from 'vue'\n\nconst firstName = ref('张')\nconst lastName = ref('三')\n\nconst fullName = computed(() => {\n  return firstName.value + lastName.value\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>{{ fullName }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n```\n\n### watch\n\n监听数据变化并执行副作用：\n\n```\n\u003Cscript setup>\nimport { ref, watch } from 'vue'\n\nconst count = ref(0)\n\nwatch(count, (newVal, oldVal) => {\n  console.log('count 变化：', oldVal, '->', newVal)\n})\n\n\u002F\u002F 监听对象属性\nconst user = ref({ name: '张三' })\nwatch(() => user.value.name, (newVal) => {\n  console.log(newVal)\n})\n\u003C\u002Fscript>\n```\n\n### watchEffect\n\n立即执行，并自动追踪依赖：\n\n```\n\u003Cscript setup>\nimport { ref, watchEffect } from 'vue'\n\nconst count = ref(0)\n\nwatchEffect(() => {\n  console.log('当前 count:', count.value)\n})\n\u003C\u002Fscript>\n```\n\n## 7. 组件基础\n\n### 定义组件\n\n子组件 `Child.vue` ：\n\n```\n\u003Cscript setup>\nconst props = defineProps({\n  title: String,\n  count: {\n    type: Number,\n    default: 0\n  }\n})\n\nconst emit = defineEmits(['update'])\n\nfunction clickBtn() {\n  emit('update', props.count + 1)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"child\">\n    \u003Ch3>{{ title }}\u003C\u002Fh3>\n    \u003Cp>数量：{{ count }}\u003C\u002Fp>\n    \u003Cbutton @click=\"clickBtn\">+1\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n父组件使用：\n\n```\n\u003Cscript setup>\nimport { ref } from 'vue'\nimport Child from '.\u002FChild.vue'\n\nconst num = ref(0)\n\nfunction onUpdate(val) {\n  num.value = val\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CChild title=\"计数器\" :count=\"num\" @update=\"onUpdate\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n### 组件通信方式\n\n|方式|说明|\n|---|---|\n|Props \u002F Emits|父子组件|\n|Provide \u002F Inject|跨层级|\n|Pinia \u002F Vuex|全局状态|\n|Event Bus|不建议，可用 mitt 替代|\n\n## 8. 生命周期钩子\n\nVue 3 的生命周期钩子名字大部分加了 `on` 前缀：\n\n|Vue 2|Vue 3（组合式 API）|说明|\n|---|---|---|\n|beforeCreate|不需要|setup 替代|\n|created|不需要|setup 替代|\n|beforeMount|onBeforeMount|挂载前|\n|mounted|onMounted|挂载完成|\n|beforeUpdate|onBeforeUpdate|更新前|\n|updated|onUpdated|更新完成|\n|beforeUnmount|onBeforeUnmount|卸载前|\n|unmounted|onUnmounted|卸载完成|\n\n常用示例：\n\n```\n\u003Cscript setup>\nimport { onMounted, onUnmounted } from 'vue'\n\nonMounted(() => {\n  console.log('组件挂载完成')\n  \u002F\u002F 可以在这里请求数据\n})\n\nonUnmounted(() => {\n  console.log('组件卸载，清理副作用')\n})\n\u003C\u002Fscript>\n```\n\n生命周期执行流程：setup → onBeforeMount → onMounted → onBeforeUpdate → onUpdated → onUnmounted。\n\n## 9. 组合式 API 深入\n\n`\u003Cscript setup>` 是 Vue 3 推荐的组合式 API 写法，所有顶层变量和函数都会自动暴露给模板。\n\n```\n\u003Cscript setup>\nimport { ref, computed, watch, onMounted } from 'vue'\n\n\u002F\u002F 状态\nconst count = ref(0)\nconst double = computed(() => count.value * 2)\n\n\u002F\u002F 方法\nfunction increment() {\n  count.value++\n}\n\n\u002F\u002F 副作用\nwatch(count, (newVal) => {\n  console.log('count 变化为:', newVal)\n})\n\nonMounted(() => {\n  console.log('组件已挂载')\n})\n\u003C\u002Fscript>\n```\n\n组合式 API 的优势在于可以把同一功能相关的代码放在一起，而不是分散在 `data` 、 `methods` 、 `computed` 里。\n\n## 10. 响应式系统原理\n\nVue 3 使用 Proxy 拦截对象操作，当数据变化时自动追踪依赖并触发更新。\n\n数据变化后的更新流程：响应式数据变化 → 触发依赖收集 → 重新生成虚拟 DOM → Diff 算法对比 → 最小化更新真实 DOM。后续数据变化会再次触发整个更新循环。\n\n## 11. 常用组合式函数\n\n|函数|用途|\n|---|---|\n|ref \u002F reactive|创建响应式数据|\n|computed|计算属性|\n|watch \u002F watchEffect|侦听器|\n|onMounted \u002F onUnmounted|生命周期|\n|provide \u002F inject|跨组件传值|\n|nextTick|DOM 更新后执行|\n\n自定义组合式函数示例（useMouse）：\n\n```javascript\n\u002F\u002F composables\u002FuseMouse.js\nimport { ref, onMounted, onUnmounted } from 'vue'\n\nexport function useMouse() {\n  const x = ref(0)\n  const y = ref(0)\n\n  function update(event) {\n    x.value = event.pageX\n    y.value = event.pageY\n  }\n\n  onMounted(() => window.addEventListener('mousemove', update))\n  onUnmounted(() => window.removeEventListener('mousemove', update))\n\n  return { x, y }\n}\n```\n\n使用：\n\n```\n\u003Cscript setup>\nimport { useMouse } from '.\u002Fcomposables\u002FuseMouse'\n\nconst { x, y } = useMouse()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>鼠标位置：{{ x }}, {{ y }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n```\n\n## 12. 路由：Vue Router\n\n安装：\n\n```bash\nnpm install vue-router@4\n```\n\n配置 `src\u002Frouter\u002Findex.js` ：\n\n```javascript\nimport { createRouter, createWebHistory } from 'vue-router'\nimport Home from '..\u002Fviews\u002FHome.vue'\nimport About from '..\u002Fviews\u002FAbout.vue'\n\nconst routes = [\n  { path: '\u002F', component: Home },\n  { path: '\u002Fabout', component: About }\n]\n\nconst router = createRouter({\n  history: createWebHistory(),\n  routes\n})\n\nexport default router\n```\n\n在 `main.js` 中挂载：\n\n```javascript\nimport { createApp } from 'vue'\nimport App from '.\u002FApp.vue'\nimport router from '.\u002Frouter'\n\ncreateApp(App).use(router).mount('#app')\n```\n\n在模板中使用：\n\n```\n\u003Ctemplate>\n  \u003Cnav>\n    \u003Crouter-link to=\"\u002F\">首页\u003C\u002Frouter-link>\n    \u003Crouter-link to=\"\u002Fabout\">关于\u003C\u002Frouter-link>\n  \u003C\u002Fnav>\n  \u003Crouter-view>\u003C\u002Frouter-view>\n\u003C\u002Ftemplate>\n```\n\n常用 API：\n\n```javascript\nimport { useRoute, useRouter } from 'vue-router'\n\nconst route = useRoute()      \u002F\u002F 当前路由信息\nconst router = useRouter()    \u002F\u002F 路由实例\n\nrouter.push('\u002Fabout')         \u002F\u002F 编程式导航\nconsole.log(route.params.id)  \u002F\u002F 获取路由参数\n```\n\n## 13. 状态管理：Pinia\n\nPinia 是 Vue 3 官方推荐的状态管理库，比 Vuex 更轻量、类型友好。\n\n安装：\n\n```bash\nnpm install pinia\n```\n\n在 `main.js` 中注册：\n\n```javascript\nimport { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from '.\u002FApp.vue'\n\ncreateApp(App).use(createPinia()).mount('#app')\n```\n\n定义 Store（ `src\u002Fstores\u002Fcounter.js` ）：\n\n```javascript\nimport { defineStore } from 'pinia'\nimport { ref, computed } from 'vue'\n\nexport const useCounterStore = defineStore('counter', () => {\n  const count = ref(0)\n  const doubleCount = computed(() => count.value * 2)\n\n  function increment() {\n    count.value++\n  }\n\n  return { count, doubleCount, increment }\n})\n```\n\n在组件中使用：\n\n```\n\u003Cscript setup>\nimport { useCounterStore } from '..\u002Fstores\u002Fcounter'\n\nconst counter = useCounterStore()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>Count: {{ counter.count }}\u003C\u002Fp>\n  \u003Cp>Double: {{ counter.doubleCount }}\u003C\u002Fp>\n  \u003Cbutton @click=\"counter.increment\">+1\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n```\n\n## 14. 完整示例：Todo List\n\n下面是一个完整的 Todo List 组件，综合运用前面所学：\n\n```\n\u003Cscript setup>\nimport { ref, computed } from 'vue'\n\nconst newTodo = ref('')\nconst todos = ref([\n  { id: 1, text: '学习 Vue 3', done: false },\n  { id: 2, text: '写一个项目', done: false }\n])\n\nconst nextId = computed(() => {\n  return todos.value.length > 0\n    ? Math.max(...todos.value.map(t => t.id)) + 1\n    : 1\n})\n\nconst remaining = computed(() => {\n  return todos.value.filter(t => !t.done).length\n})\n\nfunction addTodo() {\n  if (!newTodo.value.trim()) return\n  todos.value.push({\n    id: nextId.value,\n    text: newTodo.value,\n    done: false\n  })\n  newTodo.value = ''\n}\n\nfunction removeTodo(id) {\n  todos.value = todos.value.filter(t => t.id !== id)\n}\n\nfunction toggleTodo(todo) {\n  todo.done = !todo.done\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"todo-app\">\n    \u003Ch2>Todo List\u003C\u002Fh2>\n    \u003Cinput\n      v-model=\"newTodo\"\n      @keyup.enter=\"addTodo\"\n      placeholder=\"输入待办事项，按回车添加\"\n    >\n    \u003Cul>\n      \u003Cli v-for=\"todo in todos\" :key=\"todo.id\" :class=\"{ done: todo.done }\">\n        \u003Cinput type=\"checkbox\" :checked=\"todo.done\" @change=\"toggleTodo(todo)\">\n        \u003Cspan>{{ todo.text }}\u003C\u002Fspan>\n        \u003Cbutton @click=\"removeTodo(todo.id)\">删除\u003C\u002Fbutton>\n      \u003C\u002Fli>\n    \u003C\u002Ful>\n    \u003Cp>剩余未完成：{{ remaining }} 项\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.todo-app {\n  max-width: 400px;\n  margin: 0 auto;\n}\ninput[type=\"text\"] {\n  width: 100%;\n  padding: 8px;\n}\nli {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  padding: 8px 0;\n}\nli.done span {\n  text-decoration: line-through;\n  color: #999;\n}\nbutton {\n  margin-left: auto;\n}\n\u003C\u002Fstyle>\n```\n\n## 15. 学习建议与资源\n\n### 学习路径\n\n1. 先掌握模板语法、ref、reactive、事件和条件列表渲染\n2. 理解组件化：props、emits、插槽\n3. 学习组合式 API 和生命周期\n4. 掌握 Vue Router 和 Pinia\n5. 做一个完整小项目（如 Todo、博客后台、电商购物车）\n\n### 官方资源\n\n- [Vue 3 官方文档](https:\u002F\u002Fcn.vuejs.org\u002F)\n- [Vue Router 文档](https:\u002F\u002Frouter.vuejs.org\u002Fzh\u002F)\n- [Pinia 文档](https:\u002F\u002Fpinia.vuejs.org\u002Fzh\u002F)\n\n### 常见问题\n\n**Q： `\u003Cscript setup>` 和 `setup()` 函数有什么区别？** A： `\u003Cscript setup>` 是 `setup()` 的语法糖，代码更简洁，顶层变量自动暴露给模板。\n\n**Q：ref 和 reactive 选哪个？** A：基本类型用 ref，对象推荐 ref（可以整体替换），也可以用 reactive。\n\n**Q：组合式 API 比选项式 API 好吗？** A：没有绝对好坏。小项目用选项式更直观，中大型项目组合式更利于代码组织和复用。Vue 3 官方推荐组合式 API。\n\n> 🌹 这份教程覆盖了 Vue 3 最核心的内容。如果想深入某个部分（如 TypeScript 支持、渲染函数、自定义指令、动画等），可以继续补充展开。",38,false,"2026-10-15T21:41:45",{"list":93,"total":10,"page":20,"size":94},[],30]