1. 项目概述:为什么现在必须系统性学习Vue3?
如果你是一名前端开发者,或者正打算从前端入门,那么“Vue3”这个词在过去两年里一定反复出现在你的视野里。从2020年9月正式发布到现在,Vue3已经不再是那个需要观望的“新版本”,而是成为了构建现代Web应用的主流和默认选择。我身边很多从Vue2迁移过来的朋友,以及新入行的同事,都曾有过类似的困惑:Vue3到底要学什么?和Vue2区别大吗?是零散地看几个新特性,还是需要从头系统性地构建知识体系?我的答案是:必须系统性学习。
Vue3并非Vue2的简单增量更新,它是一次从底层到顶层的重构。Composition API的引入改变了我们组织代码逻辑的根本方式;响应式系统重写带来了更好的性能与TypeScript支持;新的生命周期、Fragment、Teleport等特性则提供了更强大的视图层描述能力。零散地学习,你可能会知道ref和reactive怎么用,但无法理解它们背后的设计哲学,更无法在复杂项目中优雅地组合它们。系统性学习的目标,是让你不仅能“用”Vue3,更能“懂”Vue3,从而在技术选型、架构设计、性能优化和团队协作中游刃有余。
这套学习路径适合所有希望掌握现代前端框架核心技能的开发者。无论你是刚入门的新手,还是拥有Vue2经验想平滑过渡的老手,或者是React、Angular开发者想拓展技术栈,一个结构清晰、由浅入深的学习体系都能让你事半功倍,避免陷入“一看就会,一写就废”的困境。
2. 学习路线图设计:从地基到高楼的全景规划
盲目地东看一篇文章,西做一个Demo,是学习效率最低下的方式。一份好的学习路线图就像建筑蓝图,能确保你的知识结构稳固且可扩展。我将Vue3的系统性学习分为四个核心阶段,每个阶段都承上启下,目标明确。
2.1 第一阶段:核心基础与开发环境搭建
这个阶段的目标是“跑起来”并理解Vue3最核心的概念。不要一上来就钻研高深原理,先获得正向反馈至关重要。
首先,是开发环境的建立。我强烈推荐使用Vite作为构建工具,而不是传统的Webpack。原因很简单:极速的热更新和构建速度能极大提升学习和开发体验。通过npm create vue@latest这个官方命令,你可以快速创建一个集成了Vue Router、Pinia、ESLint等现代工具链的项目。这一步,重点不是配置,而是理解这个脚手架为你做了什么:它如何组织项目结构(src/下的components,views,router,stores目录),以及vite.config.js的基本作用。
其次,掌握两个最核心的API:ref和reactive。这是Vue3响应式系统的基石。很多初学者会混淆它们:
ref:用于定义响应式的基本类型值(如字符串、数字、布尔值)。在脚本中需要通过.value访问,在模板中会自动解包。它的优势是明确,你知道你在操作一个响应式引用。reactive:用于定义响应式的对象。访问其属性直接使用,无需.value。但它对原始类型无效,并且整个对象替换会失去响应性。
一个常见的经验是:优先使用ref。因为它更通用,且在与Composition API的函数配合时,传递ref比传递reactive的解构属性更安全。你可以用reactive来组织一组逻辑紧密相关的状态。
最后,理解Composition API的基本形态。告别Vue2的data、methods、computed选项式API,拥抱setup()函数(或<script setup>语法糖)。<script setup>是编译时语法糖,让代码更简洁,现在是绝对的主流写法。在这个阶段,你需要学会如何在setup中定义响应式数据、计算属性(computed)、监听器(watch),以及声明生命周期钩子(如onMounted)。
注意:不要在第一阶段就试图弄懂
setup函数的所有细节。先把它当作一个“所有组合式逻辑的入口”,能写出来、跑通功能即可。深层的原理(如响应式依赖收集、渲染上下文)留到后面。
2.2 第二阶段:核心生态与组件化深度实践
当你能用ref、reactive和<script setup>写一个简单的待办事项列表后,就需要向“工程化”和“复用性”迈进了。这个阶段围绕组件化和状态管理展开。
组件通信是重中之重。Vue3的组件通信方式更加清晰:
- Props / Emits:父子组件通信的基础。Vue3中
defineProps和defineEmits是编译器宏,无需导入,直接在<script setup>中使用。务必为Props声明明确的类型(使用TypeScript或运行时类型),这是保证组件健壮性的第一步。 - v-model:Vue3中
v-model可以绑定多个,实际上是modelValueProp和update:modelValue事件的语法糖。理解并自定义v-model能让你创建出表单类组件的“双向绑定”体验。 - Provide / Inject:跨层级组件通信的利器。在祖先组件使用
provide提供数据或方法,在后代组件使用inject注入。适用于主题、用户信息等全局性数据。 - 模板引用 (
ref) 与defineExpose:父组件需要直接调用子组件方法或访问其DOM时使用。子组件需通过defineExpose显式暴露其内部属性或方法,这是与Vue2的一个显著区别,更安全。
状态管理引入Pinia。对于任何稍具规模的应用,全局状态管理都是必须的。Vue3的官方状态管理库是Pinia,它比Vuex更简单、更符合Composition API的思维。学习Pinia,核心是理解defineStore定义仓库,以及在组件中如何使用useStore来获取和操作状态。Pinia的模块化天生友好,每个Store都是一个独立的、可组合的单元。
路由管理使用Vue Router 4。学习如何定义路由(路由表结构)、进行导航(router-link和router.push)、获取路由信息(useRoute)、操作路由(useRouter),以及掌握路由守卫(全局守卫、路由独享守卫、组件内守卫)来实现权限控制。动态路由匹配和懒加载组件(defineAsyncComponent)也是提升应用性能的关键。
2.3 第三阶段:高级特性、性能与工程化
掌握前两阶段,你已经可以开发大部分Vue3应用了。第三阶段的目标是让你写出更优雅、更高效、更专业的代码。
深入Composition API的复用:Composables。这是Vue3最强大的特性之一。将可复用的响应式逻辑抽离成一个个“组合式函数”。例如,你可以封装一个useMouse追踪鼠标位置,或一个useFetch处理数据请求。学习编写Composables的关键在于:1) 以use开头命名;2) 在函数内部使用Vue的响应式API(ref,reactive,computed,watch);3) 返回一个包含响应式状态和方法的对象。这比Vue2的mixins更灵活、更清晰,没有命名冲突和来源不明确的问题。
掌握渲染优化相关的高级特性。
- Teleport:将组件模板的一部分“传送”到DOM中其他位置。常用于模态框、通知框、全屏加载层,解决z-index和样式隔离问题。
- Suspense:异步组件加载时的优雅降级UI。允许你定义组件加载中和加载失败时的备用模板,提升用户体验。
- KeepAlive:缓存不活跃的组件实例,避免重复渲染。在标签页(Tabs)或路由切换场景下非常有用,但要注意其生命周期钩子
onActivated和onDeactivated。
性能优化意识培养。了解哪些操作是昂贵的:大型响应式对象、深度监听、不必要的组件重新渲染。学习使用shallowRef和shallowReactive来避免深层响应式转换的开销;使用watch的flush: 'post'选项在DOM更新后执行副作用;理解v-memo(Vue 3.2+)这个指令,它可以条件性地跳过大型v-for列表的更新。
拥抱TypeScript。Vue3的源码就是用TypeScript编写的,它对TS的支持是原生的。即使你之前没用过TS,也强烈建议在这个阶段开始学习。从为组件的Props和Emits定义类型开始,再到为Composables的输入输出定义类型,TS能提供无与伦比的代码智能提示和重构安全性,是开发大型应用的必备技能。
2.4 第四阶段:原理浅析与生态整合
这个阶段是拔高,让你从“使用者”变为“理解者”。
理解响应式原理(浅层)。不需要立刻去读源码,但要理解核心概念:Effect(副作用)、ReactiveEffect(响应式副作用)、Dep(依赖收集)。明白ref和reactive背后是基于Proxy的代理,它如何拦截get和set操作来进行依赖收集和触发更新。了解computed和watch是如何基于这个响应式系统工作的。这能帮助你在遇到一些响应式数据“失效”的诡异问题时,快速定位原因。
了解虚拟DOM与Diff算法。知道Vue的渲染流程:模板编译成渲染函数 -> 渲染函数执行生成虚拟DOM(VNode Tree) -> 将新旧VNode进行对比(Diff) -> 计算出最小更新操作应用到真实DOM。Vue3的Diff算法优化了静态节点提升、Patch Flag标记等,这是其性能优于Vue2的重要原因之一。
生态整合与实践。学习将Vue3与UI组件库(如Element Plus、Ant Design Vue、Vant)结合使用。更重要的是,理解如何与后端协作,在“SpringBoot Vue前后端分离”或“Node.js Vue”的架构中,进行API联调、环境变量管理、Axios拦截器封装、错误统一处理等。这才是真正完成一个完整项目闭环的关键。
3. 核心概念深度解析与避坑指南
掌握了学习路径,我们还需要深入几个最容易混淆和出错的核心概念,这些是区分“会用”和“用好”的关键。
3.1refvsreactive:不只是.value的区别
选择ref还是reactive,是每个Vue3开发者每天都要做的决定。除了基础用法,更深层的区别在于:
- 类型约束与IDE支持:
ref对类型的推断非常完美,const count = ref(0)能明确推断出count是Ref<number>。而reactive在处理复杂嵌套对象时,有时需要显式定义泛型才能获得更好的类型提示。 - 解构与响应性丢失:这是
reactive最大的“坑”。当你从reactive对象中解构出一个基本类型属性时,该属性会失去响应性连接。
解决方案是使用const state = reactive({ count: 0 }); let { count } = state; // count 现在是值拷贝,不是响应式的 count++; // 不会更新视图,也不会影响原始的 state.counttoRefs将reactive对象转换为普通对象,但其每个属性都是ref:const state = reactive({ count: 0, name: 'Vue' }); const { count, name } = toRefs(state); // 现在 count 和 name 都是 ref,响应式连接保持 count.value++; // 视图更新,state.count 也同步改变 - 整体替换:
reactive对象的引用被整个替换时,也会失去响应性。而ref通过.value赋值,始终指向同一个响应式引用。
我的实操心得是:在组件内部组织状态时,对于简单的、独立的数据,优先用ref。对于逻辑上紧密关联、总是一起变化的一组状态(如表单字段),可以使用reactive,但要注意解构问题。在组合式函数(Composables)中向外暴露状态时,统一返回ref,这样使用者可以无脑地用.value访问,API更一致。
3.2<script setup>的革命性与细节
<script setup>不仅仅是语法糖,它改变了组件的编写范式。
编译器宏:defineProps,defineEmits,defineExpose,withDefaults这些在<script setup>中直接可用的函数是编译器宏。它们在编译阶段被处理,不会作为真正的函数存在于运行时。这意味着你不能动态地定义它们(例如在条件语句中)。
顶层的绑定会暴露给模板:在<script setup>中声明的变量、函数、import导入的内容,都会默认暴露给模板使用。这带来了极大的便利,但也需要注意命名冲突。如果你想隐藏某个内部变量,可以将其放在非顶层的闭包中。
与普通<script>共存:一个单文件组件(SFC)可以同时有<script setup>和普通的<script>。通常,普通的<script>用于声明组件选项,如name、inheritAttrs等,这些选项无法在<script setup>中定义。它们会合并。
访问组件实例:在<script setup>中,没有this。如何获取组件实例?在某些极少数需要它的场景下(例如需要调用一个选项式API子组件的方法),可以使用getCurrentInstance()API,但官方不推荐在应用代码中频繁使用,因为它主要服务于库开发者。
3.3 生命周期钩子的变化与使用时机
Vue3的生命周期钩子都以onXxx的函数形式在setup中调用,与选项式API有对应关系,但有两个重要的新增和变化:
onBeforeUnmount替代beforeDestroyonUnmounted替代destroyed- 新增
onRenderTracked和onRenderTriggered:用于调试,追踪响应式依赖的收集和触发。
最关键的是生命周期的执行顺序:在setup中同步调用的生命周期钩子,会在选项式API的对应钩子之前执行。例如,setup()中的onMounted会在选项式的mounted之前调用。这在你混合使用两种API时需要注意。
常见问题:在onMounted中访问DOM元素。确保模板引用(ref)已经在模板中定义,并且onMounted的调用是同步的(而不是在异步函数内部)。一个可靠的模式是:
<script setup> import { ref, onMounted } from 'vue' const inputRef = ref(null) onMounted(() => { // 此时DOM已挂载,inputRef.value 是真实的 <input> 元素 if (inputRef.value) { inputRef.value.focus() } }) </script> <template> <input ref="inputRef" /> </template>3.4 组合式函数(Composables)的设计模式
编写一个好的组合式函数,是驾驭Vue3高级能力的标志。它不仅仅是逻辑提取,更是一种设计模式。
1. 输入参数灵活:Composables可以接受ref或原始值作为参数。为了同时支持两者,内部可以使用unref工具函数:
import { unref, watch } from 'vue'; export function useFeature(target) { // unref: 如果target是ref,返回其.value;否则返回target本身 const unwrappedTarget = unref(target); // 后续逻辑基于 unwrappedTarget... watch(() => unref(target), (newVal) => { // 这样写,无论传入的是ref还是普通值,watch都能正常工作 }); }2. 返回值约定俗成:返回一个包含多个ref和方法的普通对象,方便使用者解构。返回reactive对象虽然可以,但使用者解构时会遇到响应性丢失问题,不如返回多个ref清晰。
3. 副作用清理:如果Composable内部设置了事件监听器、定时器或订阅,务必在onUnmounted钩子中清理,或者返回一个清理函数。
export function useEventListener(target, event, callback) { const el = unref(target); el?.addEventListener(event, callback); // 返回一个清理函数 const stop = () => { el?.removeEventListener(event, callback); }; // 同时,在组件卸载时也自动清理(更友好) onUnmounted(stop); return { stop }; }4. 状态隔离:每次调用useXxx()组合式函数,它内部的ref和reactive都是独立的。这保证了它在多个组件中使用时状态不会互相污染,这是优于Vue2mixins的核心点。
4. 实战:构建一个可复用的数据表格组件
让我们将上述知识融会贯通,实战构建一个支持动态列、排序、分页的通用数据表格组件。我们将使用<script setup>、Composables、defineProps/defineEmits、插槽等特性。
4.1 组件接口设计与Props定义
首先,明确组件的输入输出。我们设计以下Props:
data:表格数据源(数组)。columns:列配置(数组,每项包含标题、数据键、宽度、是否可排序等)。pageSize:每页条数。total:数据总数(用于服务端分页)。
使用defineProps并配合TypeScript(或运行时类型)进行严格定义:
<script setup lang="ts"> import { PropType } from 'vue'; interface ColumnProps { key: string; title: string; width?: number | string; sortable?: boolean; formatter?: (value: any, row: any) => any; } const props = defineProps({ data: { type: Array as PropType<any[]>, required: true, default: () => [] }, columns: { type: Array as PropType<ColumnProps[]>, required: true }, pageSize: { type: Number, default: 10 }, total: { type: Number, default: 0 } }); </script>4.2 使用Composables封装排序与分页逻辑
我们将排序和分页的逻辑抽离成独立的组合式函数,保持组件setup区域的简洁。
useSorter组合式函数:
// composables/useSorter.js import { ref, computed } from 'vue'; export function useSorter(dataRef, sortKeyRef, sortOrderRef) { const sortedData = computed(() => { const data = dataRef.value; const key = sortKeyRef.value; const order = sortOrderRef.value; if (!key) return data; return [...data].sort((a, b) => { const aVal = a[key]; const bVal = b[key]; if (order === 'asc') { return aVal > bVal ? 1 : -1; } else { return aVal < bVal ? 1 : -1; } }); }); const toggleSort = (key) => { if (sortKeyRef.value === key) { // 同一列点击,切换升序/降序 sortOrderRef.value = sortOrderRef.value === 'asc' ? 'desc' : 'asc'; } else { // 点击新列,默认降序 sortKeyRef.value = key; sortOrderRef.value = 'desc'; } }; return { sortedData, toggleSort }; }usePagination组合式函数:
// composables/usePagination.js import { ref, computed } from 'vue'; export function usePagination(dataRef, pageSize, totalRef) { const currentPage = ref(1); // 如果是前端分页,基于当前页和数据切片 const paginatedData = computed(() => { const data = dataRef.value; const start = (currentPage.value - 1) * pageSize; const end = start + pageSize; return data.slice(start, end); }); // 总页数 const totalPages = computed(() => { // 如果传入了total(服务端分页),则用total计算 if (totalRef && totalRef.value > 0) { return Math.ceil(totalRef.value / pageSize); } // 否则用前端数据的长度计算 return Math.ceil(dataRef.value.length / pageSize); }); const goToPage = (page) => { if (page >= 1 && page <= totalPages.value) { currentPage.value = page; } }; return { currentPage, paginatedData, totalPages, goToPage }; }4.3 组件模板与事件处理
在组件中集成上述逻辑,并构建模板。我们使用作用域插槽来提供最大的灵活性,允许父组件自定义每个单元格的渲染内容。
<!-- DataTable.vue --> <template> <div class="data-table"> <table> <thead> <tr> <th v-for="col in columns" :key="col.key" :style="{ width: col.width }"> <span @click="col.sortable ? handleSort(col.key) : null"> {{ col.title }} <span v-if="sortKey === col.key" class="sort-icon"> {{ sortOrder === 'asc' ? '↑' : '↓' }} </span> </span> </th> </tr> </thead> <tbody> <!-- 使用作用域插槽,将行数据(row)和列信息(col)传递给父组件 --> <tr v-for="(row, index) in displayedData" :key="index"> <td v-for="col in columns" :key="col.key"> <slot :name="col.key" :row="row" :col="col"> <!-- 默认插槽内容:使用formatter或直接显示 --> {{ col.formatter ? col.formatter(row[col.key], row) : row[col.key] }} </slot> </td> </tr> </tbody> </table> <!-- 分页器 --> <div class="pagination"> <button @click="goToPage(currentPage - 1)" :disabled="currentPage === 1">上一页</button> <span>第 {{ currentPage }} 页 / 共 {{ totalPages }} 页</span> <button @click="goToPage(currentPage + 1)" :disabled="currentPage === totalPages">下一页</button> </div> </div> </template> <script setup> import { ref, computed } from 'vue'; import { useSorter } from './composables/useSorter'; import { usePagination } from './composables/usePagination'; const props = defineProps({ /* ... 同上 ... */ }); const emit = defineEmits(['sort-change', 'page-change']); // 排序状态 const sortKey = ref(''); const sortOrder = ref('asc'); // 'asc' | 'desc' // 使用组合式函数 const { sortedData, toggleSort } = useSorter( computed(() => props.data), sortKey, sortOrder ); const { currentPage, paginatedData, totalPages, goToPage } = usePagination( sortedData, // 将排序后的数据传入分页 props.pageSize, computed(() => props.total) ); // 最终展示的数据 const displayedData = computed(() => { // 如果有服务端分页(total>0),则直接使用原始数据(假设已分页),否则使用前端分页结果 return props.total > 0 ? props.data : paginatedData.value; }); const handleSort = (key) => { toggleSort(key); emit('sort-change', { key: sortKey.value, order: sortOrder.value }); }; const handlePageChange = (page) => { goToPage(page); emit('page-change', { page, pageSize: props.pageSize }); }; </script>4.4 父组件中使用与插槽自定义
现在,我们可以在父组件中使用这个高度可配置的表格组件。
<!-- ParentComponent.vue --> <template> <DataTable :data="tableData" :columns="columns" :page-size="10" :total="total" @sort-change="handleSortChange" @page-change="handlePageChange" > <!-- 自定义“状态”列的渲染 --> <template #status="{ row }"> <span :class="`status-badge status-${row.status}`"> {{ row.status === 'active' ? '活跃' : '禁用' }} </span> </template> <!-- 自定义“操作”列 --> <template #actions="{ row }"> <button @click="editItem(row)">编辑</button> <button @click="deleteItem(row)">删除</button> </template> </DataTable> </template> <script setup> import { ref } from 'vue'; import DataTable from './DataTable.vue'; const tableData = ref([ { id: 1, name: '张三', age: 28, status: 'active' }, { id: 2, name: '李四', age: 35, status: 'inactive' }, // ... 更多数据 ]); const total = ref(50); // 假设服务端返回的总数 const columns = ref([ { key: 'name', title: '姓名', sortable: true }, { key: 'age', title: '年龄', sortable: true }, { key: 'status', title: '状态' }, { key: 'actions', title: '操作' } ]); const handleSortChange = (sortParams) => { console.log('排序变化:', sortParams); // 这里可以触发向服务端请求排序后的数据 }; const handlePageChange = (pageParams) => { console.log('页码变化:', pageParams); // 这里可以触发向服务端请求对应页的数据 }; const editItem = (row) => { /* ... */ }; const deleteItem = (row) => { /* ... */ }; </script>通过这个实战案例,你将综合运用Props/Emits、<script setup>、组合式函数、计算属性、作用域插槽等核心知识。这种组件设计模式(Props定义接口、Composables封装逻辑、插槽提供扩展性)是构建高质量Vue3组件库的通用思路。
5. 进阶:性能优化与最佳实践
当应用变得复杂时,性能问题会逐渐浮现。以下是一些关键的优化手段和日常开发中的最佳实践。
5.1 列表渲染优化:v-for的钥匙与v-memo
v-for渲染长列表是性能敏感区。首要原则:始终为v-for提供唯一的key。这个key最好是数据中的唯一标识(如id),而不是数组索引index。稳定的key能帮助Vue更高效地复用DOM元素。
对于渲染开销极大的大型列表(如成百上千行,每行结构复杂),Vue 3.2引入了v-memo指令。它可以条件性地跳过子树的更新。
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"> <p>名字: {{ item.name }}</p> <p>描述: {{ heavyComputation(item.description) }}</p> <button @click="selectItem(item)">选择</button> </div>在这个例子中,v-memo="[item.id === selected]"意味着只有当item.id === selected这个条件的结果发生变化时(即该行被选中或取消选中),整个div才会重新渲染。否则,即使item.name或item.description变了(但选中状态未变),Vue也会跳过这行的比对和更新。这对于渲染成本高的行非常有效。
5.2 响应式数据优化:避免不必要的深度转换
Vue的响应式系统默认是深度的。对于一个大型的、嵌套很深的对象使用reactive,Vue会递归地将其所有属性都转换为响应式,这可能会带来不必要的性能开销。
使用
shallowRef和shallowReactive:当你确信只需要跟踪对象引用的变化,而不关心其内部嵌套属性的变化时,可以使用它们。const shallowObj = shallowReactive({ a: { b: 1 } }); shallowObj.a.b = 2; // 这个变化不会被响应式系统追踪到! shallowObj.a = { b: 3 }; // 这个变化会被追踪到这常用于第三方类库的实例、大型不可变数据(如渲染配置)等场景。
谨慎使用
watch的深度监听:watch(source, callback, { deep: true })会深度遍历整个对象,在大型对象上代价高昂。如果可能,尽量指定具体的监听路径。// 不推荐:深度监听整个大对象 watch(() => state.largeObject, () => { ... }, { deep: true }); // 推荐:只监听需要的具体属性 watch(() => state.largeObject.specificField, () => { ... });
5.3 组件设计模式:提升可维护性
- 单一职责:一个组件只做一件事。如果一个组件变得过于庞大(模板、脚本、样式都很长),考虑将其拆分为多个更小的、专注于特定功能的子组件。
- Props向下,Events向上:数据流保持清晰。父组件通过Props传递数据给子组件,子组件通过Events通知父组件状态变化。避免使用
$parent/$children或直接修改Prop(使用.sync或v-model的更新事件除外)。 - 使用Composables抽取业务逻辑:将数据获取、表单验证、事件处理等逻辑从组件中抽离到组合式函数中。这使得组件本身更专注于视图渲染,逻辑更易于测试和复用。
- 为组件提供清晰的接口文档:即使只是项目内部组件,也应在组件头部用注释说明Props、Emits、Slots的用途和类型。如果使用TypeScript,这就是类型定义本身。
5.4 与TypeScript的深度集成
TypeScript能极大提升Vue3开发体验和代码质量。除了为Props和Emits定义类型,还有几个高级技巧:
为Composables提供泛型支持:让你的组合式函数更灵活。
// 一个通用的数据获取Composable function useFetch<T>(url: string) { const data = ref<T | null>(null); const error = ref(null); // ... 获取逻辑 return { data, error }; } // 使用时 interface User { id: number; name: string; } const { data: user } = useFetch<User>('/api/user/1'); // user的类型是 Ref<User | null>使用
typeof和keyof进行类型推导:避免重复定义类型。const defaultUser = { id: 0, name: '', age: 18 }; // 直接从值推导出类型 type User = typeof defaultUser; // 在组件Props中使用 const props = defineProps<{ user: User; editableFields: (keyof User)[]; // 只能是 'id' | 'name' | 'age' }>();使用Volar插件:在VS Code中,务必安装Volar扩展来替代Vetur。Volar为Vue3和
<script setup>提供了完美的TypeScript语言服务支持,包括模板内表达式类型检查、Props自动补全等。
系统性学习Vue3是一个循序渐进、不断实践的过程。从理解ref和reactive的基础,到熟练运用Composition API和组合式函数解构复杂逻辑,再到关注性能优化和工程化最佳实践,每一步都建立在扎实的前一步之上。我个人的体会是,不要惧怕一开始的思维转换,多写、多重构、多阅读优秀的开源代码(如Vue Use库),是掌握Vue3精髓最快的方式。最后,记住框架是工具,解决问题的思路和清晰的代码结构才是开发者真正的价值所在。