1. 整体设计与定位这份Vue笔记要解决什么问题接触Vue也几年了从当年2.x的options写法一路写到3.x的composition风格中间踩过的坑、绕过的弯零零散散记了不少东西。身边经常有朋友问我“Vue到底怎么学才高效笔记记了厚厚一本真到做项目还是懵。”其实问题不在于记不记笔记而在于笔记里记的是什么。我自己的体会是一份真正有用的Vue笔记绝不是API文档的搬家也不是把所有官方示例抄一遍就完事。它应该回答三件事第一Vue的核心机制到底是什么为什么这样设计第二真实项目里最常遇到的那些场景代码到底应该怎么写第三出问题的时候按照什么思路去定位和解决。这份笔记我按这个思路整理了很长时间。内容包括响应式系统的底层原理、组件通信的完整图谱、生命周期与副作用管理、Composition API 的实战姿势、状态管理的选型逻辑、性能优化的落地手段最后附一份高频问题的排查手册。适合正在学Vue想建立系统认知的开发者也适合已经写了段时间但总觉得哪里没学透的兄弟。看完如果能让你少翻几次搜索引擎、少踩几个隐性坑这份笔记就没白写。先说清楚一件事笔记里所有的代码示例都基于Vue 3.x部分会对比Vue 2.x的写法差异。版本差异是很多bug的根源后文会单独说。2. 响应式体系的底层逻辑2.1 Proxy 与 defineProperty 的本质区别Vue 3 的响应式核心换成 Proxy这是整个框架升级最底层的变化。Vue 2 用 Object.defineProperty 逐个拦截对象的属性读写这带来几个先天限制新增属性不会触发更新必须用 Vue.set通过索引直接修改数组元素的某个下标也不会触发更新对象层级很深时初始化要递归遍历所有属性做劫持性能开销大。Vue 3 直接对整个对象做 Proxy 代理读取和写入都走拦截逻辑所以新增属性天然就是响应式的而不再需要额外调用 API。数组的索引修改、length 变化这些 Vue 2 时代的痛点也一并解决了。深层响应不再是递归一次性完成的而是在属性被访问到时才去建立代理这个称之为懒响应初始化的性能比 Vue 2 好很多。这里给个最简单的对照示例// Vue 2 方式——新增属性需要特殊处理 const vm new Vue({ data: { user: { name: Tom } } }) // 这样不响应 vm.user.age 20 // 必须这样 Vue.set(vm.user, age, 20) // Vue 3 方式——直接赋值即可 const state reactive({ user: { name: Tom } }) state.user.age 20 // 响应式生效注意虽然 Proxy 解决了新增属性问题但如果把整个对象重新赋值比如state.user newObjstate 本身是响应式的可以拦截这个写入但 newObj 传入 reactive 时会包装为其代理对象。这里有一个很小的坑从 Vue 3 的响应式对象上解构出来的普通变量会失去响应式需要toRefs或toRef来保持。2.2 ref 与 reactive 的取舍规则很多初学者纠结一个问题到底什么时候用 ref什么时候用 reactive网上各种说法都有我按照实际踩坑经验总结出一条简单规则基本类型用 ref对象和数组按照是否需要对内部成员做深层次修改来决定。但更省心的方案是统一用 ref 包裹一切因为 ref 内部其实就是reactive({ value: ... })它同样能处理对象。ref 的优点在于语义统一不管是数字、字符串、对象还是数组写起来都是.value。模板里自动解包不用写 value。reactive 的好处是对象操作时不用加.value代码看起来更轻但在解构、传参时非常容易意外丢失响应式整个团队如果水平参差很难保证人人都注意到这个问题。我自己的项目实际代码里业务状态几乎全用 ref只是在不同层级的组合式函数内部返回时会看情况决定是否展开。import { ref, reactive, toRefs } from vue // 推荐统一用 ref const count ref(0) const formData ref({ name: , tags: [] }) formData.value.tags.push(a) // 仍然响应式 // 如果你偏爱 reactive解构时不要忘记 toRefs const state reactive({ count: 0, list: [] }) const { count, list } toRefs(state) // 这样解构出来才是响应式的 count.value2.3 响应式丢失的常见陷阱响应式丢失是 Vue 3 笔记里必须单开一节的内容。最常见的三种情况第一种是解构丢失。const { name } formData.value这之后 name 就是个普通字符串怎么改都不触发更新。第二种是函数传参丢失把一个 ref 或 reactive 对象的成员作为参数传进普通函数函数内部直接改参数同样不响应。第三种是赋值覆盖把整个响应式对象赋值给了普通变量或者反过来把普通对象的属性赋给响应式对象。这三种情况的根因都一样响应式的核心是代理对象本身脱离了代理对象的操作就是普通操作。解决方案也很固定要么用toRefs把每个属性转成单独的 ref 再解构要么在组合式函数里始终返回 ref 让调用方保持引用。排查手法上我一般会在浏览器里打印一下数据看值是对象还是 Proxy。如果打印出来是纯对象、没有 [[Handler]] 之类的东西那八成已经丢失响应式了。这种检查在改完代码后随手做一遍比反复刷新页面看效果要快得多。3. 组件通信的完整图谱3.1 父子通信props 与 emit 的标准姿势父子通信是 Vue 最基础的能力但实际项目里很多人写得不够严谨。props 方面有几个经验命名统一使用 camelCase 声明模板中使用 kebab-case 传入。布尔类型的 props 要注意默认值不要默认 true 还是 false 靠猜。props 永远不要直接修改Vue 3 的约束比 Vue 2 更严格直接改会告警真正要做的是通过 emit 让父组件去改。emit 的命名建议统一格式比如update:xxx配合v-model:xxx这样父子绑定写起来非常顺。用法上有个v3小技巧子组件声明const props defineProps([modelValue])和const emit defineEmits([update:modelValue])父组件直接Child v-modelparentData /这就实现了自定义组件的双向绑定。!-- 子组件 Child.vue -- script setup const props defineProps({ modelValue: { type: String, default: } }) const emit defineEmits([update:modelValue]) function update(val) { emit(update:modelValue, val) } /script template input :valuemodelValue inputupdate($event.target.value) / /template3.2 跨层级通信provide/inject 与依赖注入的实战边界跨层级通信有两条路线Vuex/Pinia 这类全局状态库以及 provide/inject 这种依赖注入。provide/inject 的定位很精确解决深层组件需要读取上层数据、但中间组件完全不需要关心这份数据的场景。一个典型的业务场景Page 页面组件加载了用户权限信息内部几十层深的某个按钮组件需要根据权限决定是否渲染。如果每一层都通过 props 传中间组件都得声明与自身业务无关的 props极其恶心。provide/inject 就能让 Page 直接给按钮组件提供数据。但要有边界意识provide/inject 是链路式的一个组件 provide 了数据它下面所有的后代都能 inject容易出现“我的数据到底是从哪个祖先注入过来的”这种问题。项目大了以后我习惯把所有 provide 的 key 用 Symbol 定义到一个单独文件里防止命名冲突也让注入关系一目了然。// injection-keys.js export const PERMISSION_KEY Symbol(permission) export const USER_INFO_KEY Symbol(userInfo) // 祖先组件 import { provide } from vue provide(PERMISSION_KEY, permissionData) // 后代组件 import { inject } from vue const permission inject(PERMISSION_KEY)注意inject 如果取不到值默认情况下会得到 undefined。建议用inject(PERMISSION_KEY, null)给定默认值避免 undefined 进一步参与逻辑判断导致报错这种问题排查起来挺浪费时间。3.3 事件总线在 Vue 3 里的替代方案Vue 3 移除了$on/$off实例方法这导致很多 Vue 2 时期用事件总线做跨组件通信的老写法直接失效。有些团队会引入第三方库重新实现事件总线技术上没问题但从项目维护角度看我不建议。事件总线的缺点是事件名散布在代码各处项目大了根本查不到是谁发的、谁在听出了 bug 很难追踪。替代方案按场景划分兄弟组件之间没有共享父级状态的情况下可以考虑用一个共享的响应式模块文件类似一个极简 store如果交互逻辑复杂、多个组件之间同步状态直接上 Pinia如果只是简单通知式通信比如 A 组件通知 B 组件刷新用 provide 传一个回调函数或者事件注册器也行范围清晰。下面是一个极简的共享模块方式// shared.js import { ref } from vue export const sharedData ref({ counter: 0 }) // 任意组件 import { sharedData } from ./shared sharedData.value.counter // 引用了同一个 ref天然双向响应这种方案没有魔法、没有全局注册import 进来的对象就是同一个引用改动即时生效调试非常直观。4. 生命周期与副作用管理4.1 组合式 API 下的生命周期映射Vue 3 组合式 API 把生命周期钩子全部改名换面很多人刚切换时容易懵。其实对应关系很明确beforeCreate 和 created 被 setup 取代。setup 在组件创建时立即执行此时可以访问 props、emit、 slots、attrs但不能访问组件实例 this这是和 Vue 2 最大的心智差异。onBeforeMount 对应 beforeMountonMounted 对应 mounted。onBeforeUpdate 对应 beforeUpdateonUpdated 对应 updated。onBeforeUnmount 对应 beforeDestroyonUnmounted 对应 destroyed。Vue 2 里 destroyed 钩子改为 unmounted名字含义更准确。另一个常见需求场景是 onActivated / onDeactivated / onErrorCaptured对应 keep-alive 组件激活、失活以及后代组件报错捕获这些在列表页缓存这类场景里非常重要。有个实用技巧在 setup 里定义的异步逻辑如果绑定了 onBeforeUnmount 清理一定记得清理的是同一个作用域。比如 setTimeout 的句柄、事件监听的解绑函数、轮询的定时器统统在 unmounted 阶段清掉不然组件销毁后定时器还在跑会造成状态泄漏和内存泄漏。import { onMounted, onBeforeUnmount } from vue let timer null onMounted(() { timer setInterval(() { // 轮询逻辑 }, 1000) }) onBeforeUnmount(() { if (timer) { clearInterval(timer) timer null } })4.2 watch 与 watchEffect 的正确打开方式watch 和 watchEffect 都是响应式系统的副反应执行器但是语义不同。watchEffect 是立即执行一次回调函数并在回调中访问到的任何响应式依赖发生变化时重新执行。它不需要显式声明依赖代码更简洁但正因为自动追踪容易不小心依赖了不该依赖的东西。watch 则是显式监听一个或几个数据源回调只在数据源变化时触发。它支持配置 immediate 立即执行、deep 深度监听。deep 的代价是递归遍历数据量大时开销不低能用 getter 返回特定字段就尽量别用 deep。实操心得展示类逻辑用 computed这个没有争议。执行操作类逻辑少用 watchEffect 多用 watch因为 watch 的触发时机是可控的。watchEffect 里如果有副作用逻辑比如发请求初始化时立即执行一次可能不是想要的行为可能造成多余请求。深挖业务场景时watch 带 immediate 可以达成同样效果还多一个 pre/post 刷新的控制能力。import { watch, ref } from vue const keyword ref() // 只有在输入停止后才请求适合搜索场景 watch(keyword, async (newVal) { if (newVal.length 2) return // 发起搜索请求 })关于 watch 和 watchEffect 触发的时机还牵扯到组件更新顺序。默认情况下 watch 回调在组件更新前触发如果你需要在 DOM 更新后再操作可以传{ flush: post }。这个细节在操作 DOM 尺寸、滚动位置时特别有用平地一声雷的坑。4.3 副作用清理的边界与错误示范副作用清理不只是定时器。我见过一个排查了很久的问题组件 A 里监听了全局鼠标事件组件销毁时事件没解绑导致后续所有页面都出现无意义的重复触发而且点击一次执行两次逻辑。这就是典型的副作用泄漏。在组合式 API 里watch 和 watchEffect 的返回值本身是个停止函数在onBeforeUnmount里调用即可不必额外维护变量const stopWatch watch(() props.id, loadData) onBeforeUnmount(() stopWatch())但如果你在一个被 keep-alive 缓存的组件里注意 unmounted 在缓存状态下是不会被触发的清理逻辑要放进 onDeactivated 处理。比如退出缓存页面时事件监听应该暂停重新激活时恢复。这块区分清楚了列表页的滚动位置保持和定时器暂停就能正确实现二分情况也不难处理。5. Composition API 与逻辑复用5.1 从 Options 到 Composition 的心智转变很多从 Vue 2 迁过来的人最大的障碍不是语法而是思维方式的调整。Options API 把所有代码按类型组织data 放数据、methods 放方法、computed 放计算属性。一个完整业务逻辑的三个部分散落在不同区块里当组件变大你会发现每个逻辑的代码被强行拆开维护时要上下翻跳非常痛苦。Composition API 按逻辑关注点组织代码一个业务功能的数据、计算、方法、副作用生命周期被组合在一起你可以把它抽象成一个组合式函数以后在别的组件里直接复用。这一点是 Vue 生态的一次不小的进步。一开始会用 Composition API 写组件时代码可能看起来像是把所有东西堆在 setup 里但这只是过渡期的自然状态。等意识到函数抽离的价值后组件代码会越来越薄业务逻辑全部下沉到组合式函数里组件只剩下模板和极少的粘合代码。这跟我之前写 React Hooks 的经验有些相似只是 Vue 的响应式系统让这套模式用起来更顺滑。5.2 自定义组合式函数的设计范式组合式函数的核心是返回一个干净的接口内部封装所有细节。设计上遵循三个原则第一命名以 use 开头。第二输入参数和返回值的类型尽量明确最好用 TypeScript 定义。第三只暴露该业务需要的状态和方法internal 的 ref、辅助函数不要在返回对象里透传。一个典型的例子是封装一个分页加载的通用逻辑列表请求、加载状态、当前页码、总页数、加载更多方法都在内部维护。任何组件只要调这个函数就能得到全部能力几十行逻辑不用重复写。import { ref } from vue export function usePagination(loadFn) { const list ref([]) const page ref(1) const pageSize ref(10) const total ref(0) const loading ref(false) const finished ref(false) async function loadMore() { if (loading.value || finished.value) return loading.value true try { const res await loadFn(page.value, pageSize.value) list.value.push(...res.list) total.value res.total finished.value list.value.length total.value page.value 1 } finally { loading.value false } } function reset() { list.value [] page.value 1 total.value 0 finished.value false } return { list, loading, finished, loadMore, reset } }这种封装的意义不只是少写代码而是把容易出错的边界逻辑防重复加载、判断是否到底、重置状态集中维护改一处全都生效。5.3 响应式工具函数在业务中的妙用除了 useXxx 自定义函数Vue 3 内置的一批响应式工具也值得仔细用起来computed、watch、customRef、triggerRef、shallowRef、shallowReactive、readonly、toRaw 等。把这些工具用对业务逻辑能少掉不少“临时状态”。shallowRef 在需要频繁修改整个大对象时性能优势明显也避免了一层层代理带来的维护负担triggerRef 用于强制触发 shallowRef 的更新跳过 proxy 的自动依赖收集。customRef 则适合做防抖搜索之类的场景可以自定义 get 和 set 时机的精确操作。import { customRef } from vue function useDebouncedRef value delay 300 { let timeout return customRef((track, trigger) ({ get() { track() return value }, set(newVal) { clearTimeout(timeout) timeout setTimeout(() { value newVal trigger() }, delay) } })) } const searchText useDebouncedRef(, 400)这段代码在每次赋值时延迟触发响应更新用户快速输入过程中不会频繁触发下游的搜索请求。这是我在实际项目中用得最顺手的一个工具函数写起来短、效果好、复用性极高。6. 状态管理的选型与实践6.1 什么时候该用状态管理库这是很多项目反复出现的问题状态管理库是不是必须的答案是否定的。如果你的状态只在组件内部或几个兄弟节点之间流转用 ref 配合 provide/inject 完全够用。滥用全局状态库会让项目状态流非常混乱出问题比不用它还难调。什么时候要考虑引入全局状态管理我的标准有三个第一同一份数据被多个不同层级、不同路由的组件共享并且这些组件是异步渲染的第二数据的更新链路比较复杂涉及异步请求、缓存、失效重试等逻辑需要清晰的 action 层来维护第三需要做到时间旅行调试或者跨组件消息同步的复杂场景。命中两条以上就值得上状态管理库不然别引入额外复杂度。6.2 Pinia 的核心概念与使用姿势Vue 3 生态下状态管理库默认推荐 Pinia。对比 Vuex 4Pinia 更轻、对 TypeScript 的支持更彻底、去掉了 mutations 概念同步和异步都可以在 actions 里直接写。一个典型的 store 定义如下import { defineStore } from pinia import { ref, computed } from vue export const useUserStore defineStore(user, () { const token ref() const userInfo ref(null) const isLoggedIn computed(() Boolean(token.value)) async function login(credentials) { const res await api.login(credentials) token.value res.token userInfo.value res.user } function logout() { token.value userInfo.value null } return { token, userInfo, isLoggedIn, login, logout } })这种 setup store 风格和组合式函数的写法几乎一致换到组件里使用非常顺滑。注意一点store 内部的 ref 在组件里访问时是要.value的但如果整个 store 实例经过 storeToRefs 解构出来会保留响应性。import { storeToRefs } from pinia const store useUserStore() const { token, userInfo } storeToRefs(store) // 保持响应式 const { login, logout } store // 方法直接从 store 上取这个storeToRefs和之前 reactive 里toRefs的原理是相通的千万别忘了否则解构出来的状态会变成普通值改动不触发任何视图更新。我见过不止一个兄弟在这个小细节上卡了半天。6.3 跨应用状态共享与模块化组织Pinia 本身是支持多实例共享的同一个 store 在多个 Vue 应用实例里可以挂载同一个 Pinia。这种方式适合微前端或者多窗口通信原理是利用了 Pinia 内部通过 effect scope 和全局缓存共享 state。模块化组织方面更推荐按业务域而不是按页面来拆分 store比如 user、cart、settings、orders。每个 store 只负责自己的领域不要做全局的东西。跨 store 访问时直接在 action 里调用其它 store 实例的方法是没问题且推荐的做法不要为了解决同步问题把所有状态塞到一个巨型 store 里。注意Pinia 只共享状态不共享方法内执行的任意代码。换句话说如果你的需求只是让两个不相关的 Vue 实例执行同一个逻辑可以直接抽公共函数不必通过状态管理来解决。7. 性能优化的实用手段7.1 渲染优化基础computed 缓存与 v-memo这一节说的性能优化是在真实项目中能够直接落地的细节。computed 是自带缓存的计算属性只有依赖的响应式数据变化时才重新计算。大量使用 computed 代替 methods 里做计算能有效减少重复计算开销。v-memo 是 Vue 3.2 引入的指令作用是缓存一段模板的渲染结果只有当绑定的值变化时才重新渲染。典型应用场景是列表中的每项渲染成本较高而大部分项不太变化时。要注意 v-memo 和 v-for 同时使用时的绑定逻辑一般是把列表项的 id 作为 memo 依赖。div v-foritem in list :keyitem.id v-memo[item.id] !-- 复杂子组件渲染 -- ItemCard :dataitem / /div如果列表项频繁发生局部变化v-memo 反而可能因为依赖数组的比对开销增大而产生负优化。这个指令是优化手段但不是银弹需要先分析渲染瓶颈再决定是否使用。7.2 大数据列表的性能突破口渲染几千行甚至几万行数据时单纯靠 v-for 会卡到怀疑人生。这时候有两个方向虚拟滚动和分批渲染。虚拟滚动的思路是只渲染可见区域内的少量节点核心逻辑是监听容器滚动事件计算当前可见行的起始索引和结束索引动态修改渲染的数据切片。很多现成库能做这件事比如官方推荐的虚拟滚动方案或者一些社区封装组件。如果项目对体积敏感手写一个简单的虚拟滚动也不算复杂关键就是维护好 scrollTop 到 startIndex 的映射。另一种方案是分批渲染利用 requestIdleCallback 或者 setTimeout 分段插入数据。在真实项目里我处理过一个导出任务日志的页面接口一次返回 10000 条以上记录就是在实现虚拟滚动后流畅度才有明显好转的。这个优化前后的感知差别用“卡死”到“丝滑”来形容一点也不夸张。7.3 组件级优化与异步渲染策略组件层面的优化第一条是合理拆分。把一个巨型组件拆成多个职责单一的子组件不只是为了代码可读性更是为了让局部更新不需要整棵子树重新渲染。虽然 Vue 的虚拟 DOM 和响应式系统已经做了大量优化但你的更新粒度越细实际重渲染的节点越少性能越好。异步渲染策略方面defineAsyncComponent 配合 Suspense 可以做到组件级别的懒加载。路由组件懒加载 首屏资源按需分割是 SPA 性能优化最直接的手段之一。import { defineAsyncComponent } from vue const HeavyComponent defineAsyncComponent(() import(./HeavyComponent.vue) )这里有一个经验之谈不是所有组件都适合异步加载只有首屏不需要、用户操作后才有可能出现的组件才值得拆出来异步加载。比如弹窗里嵌套的复杂表单、图表组件这类用异步加载效果很好一个直接可见的头部组件强行异步反而增加了一次加载的白屏等待体验更差。7.4 内存泄漏排查思路内存泄漏是前端项目最难查的问题之一因为它不是一闪而过的 bug而是运行越久页面越卡、内存占用持续增长的慢性病。前端最常见的泄漏来源有这五类一是全局事件监听没解除二是定时器没清理三是意外把大对象保存在全局变量里四是 DOM 引用被事件闭包捕获五是单例模式里维护了无用的长驻状态。排查方法论很固定打开 DevTools 的 Memory 面板录制几次堆快照对比两次快照之间的对象增长或者用 Performance 面板记录一定时间内的操作观察内存曲线是不是持续上升不回落。前者适合定位对象泄漏后者适合定位事件累计。看到一个 Unknown 或者 detached DOM 数量大量增长的时候几乎可以断定是事件监听或 DOM 引用没有释放。我花过最长的一次内存排查耗费一整天最终定位到某个组件里注册了滚动事件但使用了箭头函数导致解绑时引用不同解绑失败。改用具名函数后问题解决。这个细节真的很值得记住代码里用removeEventListener时回调函数必须在 add 和 remove 中使用同一个引用。8. 高频问题排查与经验速查8.1 模板渲染不更新的常见起因“数据明明改了页面怎么不更新”是 Vue 社区里出现频率最高的问题。归纳下来无非这几种第一数据本身不是响应式的。引入了一个普通对象直接赋值或者从别的模块拿来的全局常量改动后 Vue 根本感知不到。检查方法很简单打印出来看是不是 Proxy。第二直接修改了 props。props 在 Vue 3 里是只读代理改了虽然数据变化但视图不更新因为 Vue 不允许这个路径。第三对象新增属性在 Vue 2 里不响应在 Vue 3 里如果是 reactive 包装的对象则已经解决但如果是 reactive 替换了整个对象却是在普通容器里触发的也会出问题。第四使用了索引赋值的方式修改数组Vue 3 的 Proxy 虽然能拦截但如果你用的是普通数组而不是 ref 或 reactive 包装的数组一样不响应。排查顺序我一般按从外到内先看数据在不在、是不是响应式的再看修改方式走没走响应式系统最后看模板引用对不对。这套顺序能覆盖绝大多数场景。8.2 Router 与 Pinia 状态持久化之间的坑Vue Router 和 Pinia 协作起来有个高频坑页面刷新之后Pinia 里所有状态全部初始化用户登录态丢失业务跳回登录页。这个问题的本质是 Pinia 默认不持久化状态只存在内存里。解决方案有三种路线。最简单的是把关键状态写入 localStorage 或 sessionStorage刷新时读取并在 store 初始化时恢复。比较稳妥的是直接集成一个持久化中间件在 store 定义时配置需要持久化的字段自动处理存和取。更繁琐但可控的是在应用入口手动恢复。我的实践观点不要把整个 store 持久化只持久化必要字段比如 token、用户ID、偏好配置。一次性塞入大量不可序列化的数据如函数、复杂对象会埋下隐患。字段粒度的持久化设计能让刷新恢复逻辑清晰也避免状态过期问题。路由守卫和持久化的时序也要注意恢复状态一定要在路由判断守卫之前完成否则用户会被误踢到登录页。// 路由守卫中判断登录态前确保 store 已经从存储恢复完成 router.beforeEach((to) { const userStore useUserStore() if (to.meta.requiresAuth !userStore.isLoggedIn) { return /login } })提前把恢复逻辑放到应用初始化时执行、保证 beforeEnter 时状态已就绪这个顺序问题能省下很多无头绪的排查。8.3 其他容易忽视的 Vue 3 变更清单除了上面提到的大项还有几个版本的破坏性变更容易让老 Vue 2 开发者踩坑v-model参数可以自定义modelValue取代了 Vue 2 的valueupdate:modelValue取代input事件。全局 API 改为实例方法Vue.use变成app.useVue.directive变成app.directive。filter 被移除数据过滤统一用计算属性或方法替代。$children被移除通过 ref 或 provide 替代。自定义事件验证被移除但 props 验证保留。异步组件写法有变化多了一个defineAsyncComponent。这份清单建议贴在项目代码库里转换老代码的时候对照着改能少踩不少无声无息的坑。我见过因为 v-model 语法变了而不自知结果输入框绑定的数据一直同步不到父组件的案例排查很久才发现是因为用法还是 Vue 2 的旧写法。9. 写给自己和同路人的几句实在话整理完这份笔记再回头看标题里那句vue笔记突然觉得这几个字的分量其实很重。一套框架学到什么程度才算真正掌握我的理解是不是能写组件、能跑起个项目就算会而是遇到一个问题时能快速定位到底是响应式链路断了、生命周期次序错了、还是状态管理设计不合理。这种判断力全靠项目实战和时间喂出来没有捷径。我给自己的要求是每完成一个项目就回头把笔记里过时的、冗余的、没经住验证的内容删掉换成这次踩坑后的新理解。笔记是活的东西不是落灰的日记本。今后再看这份笔记时希望我自己也能笑着感叹一句这坑当初还真踩得值。
阅读完成 · 觉得有帮助?