目录写在前面一、什么是VueVue的核心特性Vue的两种API风格二、快速开始创建项目单文件组件SFC三、响应式基础什么是响应式ref() - 包装基本类型reactive() - 代理对象选择指南四、模板语法文本插值 {{ }}属性绑定 :事件监听 条件渲染列表渲染 v-for双向绑定 v-model五、计算属性与侦听器computed - 缓存计算结果watch - 监听变化执行副作用watchEffect - 自动追踪依赖六、组件基础组件的概念定义与使用组件Props - 父传子Emits - 子传父v-model - 双向绑定插槽 Slots - 内容分发七、生命周期什么是生命周期常用生命周期钩子八、Vue Router - 路由管理什么是路由核心概念安装与配置路由导航路由守卫九、Axios - HTTP请求什么是Axios为什么需要封装安装与基础用法封装请求实例推荐封装API接口在组件中使用十、Pinia - 状态管理为什么需要状态管理Pinia的核心概念安装与创建Store在组件中使用修改State的三种方式十一、组合式函数 - 逻辑复用什么是组合式函数示例useMouse示例useFetch十二、TypeScript 支持为什么使用TypeScriptProps类型标注Emits类型标注Ref和Reactive类型模板引用类型十三、项目结构建议总结写在前面Vue是一款渐进式JavaScript框架核心功能是声明式渲染和响应式更新。本文将直击要害使用最精炼的语言讲解让你看完就能看懂Vue代码并上手Vue开发。一、什么是VueVue的核心特性1. 声明式渲染传统的命令式编程需要手动操作DOM获取元素→修改内容→更新样式。而Vue的声明式渲染让你只需描述页面应该长什么样Vue会自动将数据映射到DOM。!-- 声明式描述数据和视图的关系 -- p{{ message }}/p !-- 数据变化时Vue自动更新DOM无需手动操作 --2. 响应性Vue会自动追踪数据的变化当数据改变时所有依赖该数据的地方都会自动更新。这是通过Proxy代理对象实现的你对数据的任何修改都会被捕获并触发更新。Vue的两种API风格风格特点适用场景选项式API按data、methods、computed等选项组织代码小型项目、初学者组合式API按功能逻辑组织代码使用函数式写法中大型项目、逻辑复用本文使用组合式API 单文件组件SFC这是Vue 3推荐的标准开发方式。二、快速开始创建项目# 使用官方脚手架创建Vue项目 npm create vuelatest单文件组件SFCVue的单文件组件将模板、脚本、样式封装在一个.vue文件中templateHTML模板使用Vue的模板语法script setup组件逻辑setup表示使用组合式APIstyle scoped组件样式scoped表示仅作用于当前组件template !-- 模板声明式渲染HTML -- h1{{ message }}/h1 /template script setup // setup使用组合式API的标识 import { ref } from vue; // ref()创建响应式数据 const message ref(Hello Vue!); /script style scoped /* scoped样式仅作用于当前组件 */ h1 { color: blue; } /style三、响应式基础什么是响应式响应式是指数据变化时依赖该数据的视图会自动更新。Vue 3使用Proxy实现响应式可以追踪对象属性的读取和修改。ref() - 包装基本类型概念ref()将基本类型string/number/boolean包装成一个对象使其具备响应性。包装后的值需要通过.value访问。为什么需要包装JavaScript的基本类型是按值传递的无法被追踪。ref将其包装成对象通过属性访问器实现追踪。script setup import { ref } from vue; // ref()将基本类型包装成响应式对象 const count ref(0); function increment() { // script中访问需要.value count.value; } /script template !-- 模板中自动解包不需要.value -- button clickincrement{{ count }}/button /templatereactive() - 代理对象概念reactive()接收一个对象返回该对象的Proxy代理。代理会拦截属性的读取和设置操作实现响应式。与ref的区别reactive直接代理对象不需要.value但只能用于对象类型。script setup import { reactive } from vue; // reactive()将对象转为响应式 const state reactive({ count: 0, name: Vue }); function increment() { // reactive对象直接访问属性不需要.value state.count; } /script选择指南场景使用原因基本类型string/number/booleanrefreactive不支持基本类型对象/数组ref 或 reactiveref内部用reactive包装对象需要替换整个对象refreactive替换对象会失去响应性解构后保持响应性refreactive解构需用toRefs四、模板语法文本插值 {{ }}概念双大括号是Vue的模板插值语法会将里面的表达式结果渲染为文本。支持简单的JavaScript表达式。template !-- 显示数据 -- p{{ message }}/p !-- 支持表达式 -- p{{ count 1 }}/p p{{ isShow ? 是 : 否 }}/p /template属性绑定 :概念v-bind简写:用于将HTML属性与Vue数据绑定。当数据变化时属性值自动更新。template !-- : 是 v-bind: 的简写将属性与数据绑定 -- img :srcimageUrl / button :disabledisDisabled禁用/button !-- 对象语法动态切换class -- div :class{ active: isActive, text-danger: hasError }/div /template事件监听 概念v-on简写用于监听DOM事件。可以绑定方法或内联语句$event可以访问原生事件对象。template !-- 是 v-on: 的简写监听DOM事件 -- button clickhandleClick点击/button !-- 内联语句 -- button clickcount1/button !-- 传递参数 -- button clickhandleSubmit($event, 参数)提交/button /template条件渲染概念v-if根据条件决定是否渲染元素条件为false时不创建DOMv-show始终渲染只是通过CSS控制显示隐藏。选择频繁切换用v-show初始渲染开销大切换开销小条件很少改变用v-if初始渲染开销小切换开销大。template !-- v-if条件为false时不渲染DOM切换开销大 -- p v-ifisShow显示/p p v-else隐藏/p !-- v-show始终渲染只是切换display初始开销大 -- p v-showisShow总是渲染/p /template列表渲染 v-for概念v-for用于遍历数组或对象生成列表。:key是必需的性能优化项帮助Vue识别哪些元素改变了从而高效更新DOM。template !-- v-for遍历数组/对象:key是性能优化的必需项 -- ul li v-for(item, index) in list :keyitem.id {{ index }} - {{ item.name }} /li /ul /template script setup import { ref } from vue; const list ref([ { id: 1, name: 苹果 }, { id: 2, name: 香蕉 }, ]); /script双向绑定 v-model概念v-model是语法糖等价于:valueinput或对应表单元素的属性事件。实现数据与视图的双向同步。template !-- v-model :value input实现双向数据绑定 -- input v-modelmessage / !-- 复选框绑定boolean -- input typecheckbox v-modelisChecked / !-- 单选绑定选中的value -- input typeradio valueA v-modelpicked / input typeradio valueB v-modelpicked / /template script setup import { ref } from vue; const message ref(); const isChecked ref(false); const picked ref(A); /script五、计算属性与侦听器computed - 缓存计算结果概念computed用于定义基于其他数据计算得出的值。与普通函数的区别是computed有缓存只有依赖变化时才重新计算。适用场景需要根据现有数据派生出新数据且不想重复计算时。script setup import { ref, computed } from vue; const firstName ref(张); const lastName ref(三); // computed基于依赖缓存结果只有依赖变化才重新计算 const fullName computed(() { return firstName.value lastName.value; }); // 可写的计算属性 const fullName2 computed({ get: () firstName.value lastName.value, set: (val) { [firstName.value, lastName.value] val.split( ); }, }); /scriptwatch - 监听变化执行副作用概念watch用于监听特定数据的变化当数据变化时执行回调函数。适合执行异步操作或复杂逻辑。与computed的区别computed返回一个新值watch执行副作用不返回值。script setup import { ref, watch } from vue; const searchText ref(); const results ref([]); // watch监听数据源变化执行副作用如API请求 watch(searchText, async (newVal, oldVal) { // newVal: 新值, oldVal: 旧值 if (newVal.length 2) { results.value await fetchSearchResults(newVal); } }); // 监听多个数据源 watch([foo, bar], ([newFoo, newBar], [oldFoo, oldBar]) { // 同时监听foo和bar的变化 }); // 立即执行 深度监听 watch( someObject, (newVal) { /* ... */ }, { immediate: true, deep: true } ); /scriptwatchEffect - 自动追踪依赖概念watchEffect会立即执行回调并自动追踪回调中用到的所有响应式数据。当任何依赖变化时回调会重新执行。与watch的区别watch显式指定监听源watchEffect自动追踪watch可以获取新旧值watchEffect不能。script setup import { ref, watchEffect } from vue; const userId ref(1); const userData ref(null); // watchEffect自动追踪回调中用到的所有响应式依赖 // 组件挂载时立即执行依赖变化时重新执行 watchEffect(async () { // 用到了userId自动建立依赖关系 const res await fetch(/api/users/${userId.value}); userData.value await res.json(); }); /script六、组件基础组件的概念组件是Vue的核心概念将页面拆分成独立、可复用的模块。每个组件封装了自己的模板、逻辑和样式可以像搭积木一样组合成完整应用。定义与使用组件概念创建一个.vue文件即定义了一个组件在父组件中导入即可使用。组件可以复用多次每个实例有独立的状态。!-- ButtonCounter.vue -- template button clickcount点击 {{ count }} 次/button /template script setup import { ref } from vue; const count ref(0); /script!-- 父组件中使用 -- template ButtonCounter / /template script setup import ButtonCounter from ./ButtonCounter.vue; /scriptProps - 父传子概念Props是父组件向子组件传递数据的机制。子组件通过defineProps声明接收哪些props父组件通过属性绑定传递数据。单向数据流Props是只读的子组件不能直接修改props应该通过事件通知父组件修改。!-- 子组件Child.vue -- script setup // defineProps声明接收的props编译器宏无需导入 const props defineProps({ title: String, // 基础类型校验 likes: { type: Number, default: 0 }, // 带默认值 author: { type: Object, required: true }, // 必传 status: { type: String, validator: (v) [success, fail].includes(v), // 自定义校验 }, }); /script template h3{{ props.title }}/h3 /template!-- 父组件传递 -- template !-- 静态值 -- Child title标题 / !-- 动态绑定 -- Child :titledynamicTitle :likescount / /templateEmits - 子传父概念子组件通过$emit触发自定义事件父组件通过事件名监听事件。这是子组件向父组件通信的主要方式。!-- 子组件 -- script setup // defineEmits声明要触发的事件 const emit defineEmits([update, delete]); function handleUpdate() { // 触发事件传递数据给父组件 emit(update, { id: 1, name: 新名称 }); } /script template button clickhandleUpdate更新/button /template!-- 父组件监听 -- template Child updatehandleUpdate deletehandleDelete / /template script setup function handleUpdate(data) { console.log(收到更新:, data); } /scriptv-model - 双向绑定概念v-model是Props和Emits的语法糖实现父子组件的双向数据绑定。本质上是:modelValueupdate:modelValue。!-- 子组件CustomInput.vue -- script setup const props defineProps([modelValue]); const emit defineEmits([update:modelValue]); function onInput(e) { // 触发update:modelValue事件更新父组件数据 emit(update:modelValue, e.target.value); } /script template input :valuemodelValue inputonInput / /template!-- 父组件使用 -- template !-- v-model是 :modelValue update:modelValue 的语法糖 -- CustomInput v-modelmessage / /template插槽 Slots - 内容分发概念插槽允许父组件向子组件传递模板内容实现组件的灵活复用。具名插槽可以分发内容到子组件的不同位置作用域插槽可以让子组件向父组件传递数据。!-- 子组件Card.vue -- template div classcard !-- 具名插槽header -- header slot nameheader默认标题/slot /header !-- 默认插槽 -- main slot默认内容/slot /main footer slot namefooter :datafooterData/slot /footer /div /template script setup const footerData { date: 2024-01-01 }; /script!-- 父组件使用 -- template Card !-- #header 是 v-slot:header 的简写 -- template #header h2自定义标题/h2 /template !-- 默认插槽内容 -- p卡片内容/p !-- 作用域插槽接收子组件传递的数据 -- template #footer{ data } p日期: {{ data.date }}/p /template /Card /template七、生命周期什么是生命周期生命周期是组件从创建到销毁的各个阶段。Vue提供了钩子函数让你在这些阶段执行自定义逻辑。常用生命周期钩子钩子触发时机常用场景onMounted组件挂载到DOM后发起API请求、操作DOMonUpdated数据更新后响应数据变化后的操作onUnmounted组件销毁前清理定时器、事件监听script setup import { onBeforeMount, // DOM挂载前 onMounted, // DOM挂载后常用发起请求、操作DOM onBeforeUpdate, // 数据更新前 onUpdated, // 数据更新后 onBeforeUnmount,// 组件卸载前 onUnmounted, // 组件卸载后常用清理副作用 } from vue; // 挂载组件插入DOM onMounted(() { console.log(组件已挂载可以操作DOM); }); // 卸载组件从DOM移除 onUnmounted(() { console.log(组件已卸载清理定时器、事件监听等); }); /script八、Vue Router - 路由管理什么是路由路由是单页应用SPA中管理页面导航的机制。Vue Router通过URL映射到不同组件实现无刷新页面切换。核心概念路由RouteURL路径与组件的映射关系路由器Router管理所有路由的实例路由视图RouterView渲染匹配组件的容器导航守卫路由切换前后的拦截处理安装与配置npm install vue-router4// router/index.js import { createRouter, createWebHistory } from vue-router; const router createRouter({ history: createWebHistory(), // history模式无#或createWebHashHistory有# routes: [ { path: /, component: () import(/views/Home.vue) }, { path: /about, component: () import(/views/About.vue) }, { path: /user/:id, // 动态路由 component: () import(/views/User.vue), meta: { requiresAuth: true } // 路由元信息 }, ], }); export default router;// main.js import { createApp } from vue; import App from ./App.vue; import router from ./router; createApp(App).use(router).mount(#app);路由导航声明式导航使用router-link组件类似a标签但无刷新。编程式导航使用useRouter获取路由器实例调用push、replace等方法。template !-- router-link声明式导航类似a标签 -- nav router-link to/首页/router-link router-link :to{ name: User, params: { id: 123 } }用户/router-link /nav !-- router-view路由匹配组件的渲染出口 -- router-view / /template script setup import { useRouter, useRoute } from vue-router; const router useRouter(); // 路由实例用于编程式导航 const route useRoute(); // 当前路由信息 // 编程式导航 function goToUser(id) { router.push(/user/${id}); // 字符串路径 router.push({ path: /user, query: { id } }); // 带查询参数 router.push({ name: User, params: { id } }); // 命名路由 router.replace(/home); // 替换当前历史记录 router.back(); // 返回上一页 } // 获取路由参数 const userId route.params.id; // 动态路由参数 /user/:id const query route.query.search; // 查询参数 ?searchxxx /script路由守卫概念路由守卫是在路由切换前后执行的钩子函数用于权限验证、数据预加载等。// 全局前置守卫路由跳转前执行 router.beforeEach((to, from, next) { const isLoggedIn localStorage.getItem(token); // to: 目标路由, from: 来源路由 if (to.meta.requiresAuth !isLoggedIn) { next(/login); // 未登录重定向到登录页 } else { next(); // 放行 } });九、Axios - HTTP请求什么是AxiosAxios是一个基于Promise的HTTP库用于浏览器和Node.js。它提供了简洁的API来发起HTTP请求支持请求/响应拦截器、自动转换JSON等功能。为什么需要封装实际项目中直接调用axios会导致重复配置baseURL、timeout等每个请求都要处理错误无法统一添加token等认证信息因此需要封装统一的请求实例。安装与基础用法npm install axiosimport axios from axios; // 基础请求 axios.get(/api/users).then((res) console.log(res.data)); axios.post(/api/users, { name: 张三 }); // async/await const users await axios.get(/api/users);封装请求实例推荐// utils/request.js import axios from axios; const request axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, // 基础URL timeout: 10000, // 超时时间 }); // 请求拦截器在请求发送前处理如添加token request.interceptors.request.use((config) { const token localStorage.getItem(token); if (token) config.headers.Authorization Bearer ${token}; return config; }); // 响应拦截器在收到响应后处理如统一错误处理 request.interceptors.response.use( (res) res.data, // 直接返回data (err) { if (err.response?.status 401) { window.location.href /login; } return Promise.reject(err); } ); export default request;封装API接口// api/user.js import request from /utils/request; export const getUsers (params) request.get(/users, { params }); export const getUserById (id) request.get(/users/${id}); export const createUser (data) request.post(/users, data); export const updateUser (id, data) request.put(/users/${id}, data); export const deleteUser (id) request.delete(/users/${id});在组件中使用script setup import { ref, onMounted } from vue; import { getUsers, deleteUser } from /api/user; const users ref([]); const loading ref(false); async function loadUsers() { loading.value true; try { users.value await getUsers({ page: 1 }); } finally { loading.value false; } } async function handleDelete(id) { await deleteUser(id); await loadUsers(); // 删除后刷新列表 } onMounted(loadUsers); /script十、Pinia - 状态管理为什么需要状态管理当多个组件需要共享数据时通过Props层层传递Prop Drilling会变得复杂。状态管理库提供全局的、响应式的数据存储任何组件都可以访问和修改。Pinia的核心概念Store状态管理的单元类似组件但只关注状态State响应式数据Getters计算属性派生状态Actions修改状态的方法安装与创建Storenpm install pinia// stores/counter.js import { defineStore } from pinia; import { ref, computed } from vue; // defineStore(唯一ID, 函数) 创建store export const useCounterStore defineStore(counter, () { // State响应式状态 const count ref(0); // Getter计算属性 const doubleCount computed(() count.value * 2); // Action修改状态的方法 function increment() { count.value; } return { count, doubleCount, increment }; });在组件中使用script setup import { useCounterStore } from /stores/counter; import { storeToRefs } from pinia; const counter useCounterStore(); // 直接解构会失去响应性使用storeToRefs保持响应性 const { count, doubleCount } storeToRefs(counter); // 方法可以直接解构 const { increment } counter; /script template p{{ count }} / {{ doubleCount }}/p button clickincrement1/button /template修改State的三种方式const counter useCounterStore(); // 1. 直接修改简单场景 counter.count; // 2. $patch批量修改多个状态 counter.$patch({ count: 10, name: new }); // 3. 通过Action修改推荐可包含业务逻辑 counter.increment();十一、组合式函数 - 逻辑复用什么是组合式函数组合式函数Composables是利用Vue组合式API封装可复用逻辑的函数。与Mixin相比它更清晰、类型友好、无命名冲突。命名约定以use开头如useMouse、useFetch。示例useMouse概念将鼠标位置追踪逻辑封装成可复用的函数任何组件都可以使用。// composables/useMouse.js import { ref, onMounted, onUnmounted } from vue; export function useMouse() { const x ref(0); const y ref(0); function update(e) { x.value e.pageX; y.value e.pageY; } onMounted(() window.addEventListener(mousemove, update)); onUnmounted(() window.removeEventListener(mousemove, update)); return { x, y }; // 返回响应式状态 }script setup import { useMouse } from /composables/useMouse; const { x, y } useMouse(); // 复用鼠标追踪逻辑 /script template p鼠标: {{ x }}, {{ y }}/p /template示例useFetch// composables/useFetch.js import { ref, watch } from vue; export function useFetch(url) { const data ref(null); const error ref(null); const loading ref(false); async function fetch() { loading.value true; try { const res await fetch(url.value); data.value await res.json(); } catch (e) { error.value e.message; } finally { loading.value false; } } // url变化时自动重新获取 watch(url, fetch, { immediate: true }); return { data, error, loading, refetch: fetch }; }十二、TypeScript 支持为什么使用TypeScriptTypeScript为JavaScript提供静态类型检查可以在编译时发现错误提供更好的IDE支持代码提示、重构等增强代码可维护性。Props类型标注概念使用接口Interface定义Props的结构通过泛型传递给defineProps实现类型检查和代码提示。script setup langts // 定义Props接口 interface Props { title: string; // 必传 count?: number; // 可选 items: { id: number; name: string }[]; } // 泛型方式声明props const props definePropsProps(); // 带默认值 const props2 withDefaults(definePropsProps(), { count: 0, }); /scriptEmits类型标注script setup langts // 定义Emits类型 const emit defineEmits{ change: [id: number]; // 事件名: [参数类型] update: [value: string]; }(); emit(change, 123); // 类型检查参数必须是number /scriptRef和Reactive类型script setup langts import { ref, reactive } from vue; // ref自动推断类型 const count refnumber(0); const status refloading | success(loading); // 定义接口 interface User { id: number; name: string; } const user refUser({ id: 1, name: }); const user2 reactiveUser({ id: 1, name: }); /script模板引用类型script setup langts import { ref, onMounted } from vue; // 标注DOM元素类型 const inputRef refHTMLInputElement | null(null); onMounted(() { inputRef.value?.focus(); // 类型安全访问 }); /script template input refinputRef / /template十三、项目结构建议src/ ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── views/ # 页面组件 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── composables/ # 组合式函数 ├── api/ # 接口封装 ├── utils/ # 工具函数 ├── App.vue └── main.ts总结Vue 3 核心知识体系模块核心API作用响应式ref/reactive创建响应式数据模板{{ }}/://v-model声明式渲染、绑定、事件组件Props/Emits/Slots组件通信计算computed/watch派生数据、副作用路由Vue Router单页应用导航请求AxiosHTTP数据交互状态Pinia全局状态管理复用Composables逻辑复用类型TypeScript类型安全