Vue 3发布已经好几年了,我从Vue 3.0的beta版本就开始用,一直用到现在的3.4+。

这三年里,用Vue做了大大小小十几个项目,从简单的后台管理系统到复杂的单页应用,踩了不少坑,也积累了一些经验。

这篇文章,我想聊聊那些用了很久之后才明白的道理。这些道理,可能在文档里就有,但只有真正踩过坑之后,才能深刻理解。

道理一:组合式API不是银弹

Vue 3最大的变化就是引入了组合式API(Composition API)。

刚开始用组合式API的时候,我觉得它比选项式API(Options API)好太多了。逻辑可以复用,代码可以组织得更灵活,类型推断也更好。于是,我所有的项目都用组合式API,所有的组件都用setup函数。

但用了一段时间之后,我发现组合式API也有它的问题。

第一个问题是,代码组织不好的话,会比选项式API更乱。选项式API把data、methods、computed、watch分开放,虽然逻辑分散,但至少结构清晰。组合式API把所有东西都放在setup里,如果组织不好,一个setup函数可能有几百行,各种变量和函数混在一起,比选项式API还难维护。

第二个问题是,不是所有组件都适合用组合式API。对于一些简单的组件,比如展示型组件,选项式API其实更简洁、更易读。组合式API的优势在复杂组件、需要逻辑复用的场景下才能体现出来。

第三个问题是,响应式数据的解构问题。用reactive创建的对象,解构之后会失去响应式。这个坑很多人都踩过,包括我自己。后来才知道要用toRefs或者直接用ref。

所以,我的建议是:不要盲目追求组合式API。根据组件的复杂度和需求,选择合适的写法。简单组件用选项式API,复杂组件用组合式API,两者结合才是最佳实践。

道理二:ref和reactive要选对

ref和reactive是Vue 3中最常用的两个响应式API,但很多人(包括我自己)一开始都分不清什么时候用哪个。

我一开始的习惯是,简单值用ref,对象用reactive。但后来发现,这个规则并不总是适用。

ref的优势是,它可以包裹任何类型的值,而且访问的时候需要用.value,这让响应式数据的来源很清晰。ref的劣势是,在模板中使用的时候会自动解包,但在JS中使用的时候要记得加.value,有时候会忘。

reactive的优势是,访问属性的时候不需要.value,用起来更自然。reactive的劣势是,只能包裹对象,不能包裹原始值;而且解构之后会失去响应式,这是最容易踩坑的地方。

用了三年之后,我的习惯是:大部分情况下都用ref,包括对象。因为ref的行为更一致,不会有解构丢失响应式的问题。只有在一些特定的场景下,比如表单数据,才会用reactive,因为表单数据通常是一个整体,不会解构。

还有一个技巧是,用ref包裹对象的时候,可以配合shallowRef使用,减少不必要的深度响应式,提升性能。

道理三:不要过度使用响应式

Vue的响应式系统很强大,但这并不意味着所有数据都应该是响应式的。

我一开始的习惯是,所有数据都用ref或者reactive包裹,觉得这样才是"Vue的方式"。但后来发现,过度使用响应式会带来性能问题和代码复杂度。

比如,一些不需要在模板中使用的数据,或者一些只在组件内部使用、不会触发视图更新的数据,完全可以用普通的变量,不需要用响应式。

还有一个常见的过度使用是,把大对象或者大数组完全变成响应式的。Vue 3的响应式是深度的,也就是说,对象的每一层属性都会被代理。如果对象很大、嵌套很深,这个代理的开销是不小的。

我的建议是:

  • 只在需要在模板中使用或者需要触发更新的数据上使用响应式
  • 对于大对象,可以用shallowRef或者shallowReactive,只做浅层响应式
  • 对于不需要响应式的数据,直接用普通变量
  • 合理使用markRaw,把不需要响应式的对象标记为原始对象

响应式是Vue的核心优势,但好东西也要用在刀刃上。

道理四:computed比watch更常用

刚开始用Vue 3的时候,我用了很多watch。数据变了,就用watch监听,然后做一些处理。

但后来我发现,很多时候用computed比用watch更好。

computed的优势是:

  • 它是声明式的,你只需要描述数据之间的关系,不需要手动处理更新
  • 它有缓存,只有依赖变化的时候才会重新计算
  • 它可以在模板中直接使用,不需要额外的变量

而watch的优势是:

  • 可以执行副作用,比如发送请求、操作DOM
  • 可以访问变化前后的值
  • 可以设置immediate和deep等选项

我的经验是:如果一个数据是由其他数据计算得来的,而且需要在模板中使用,那就用computed。如果需要在数据变化时执行一些副作用(比如发请求、存localStorage),那就用watch。

我见过很多代码,用watch监听一个数据,然后把结果赋值给另一个数据。这种情况完全可以用computed来替代,代码更简洁,也更不容易出错。

还有一个常见的错误是,在watch里修改其他响应式数据,然后又用另一个watch监听那个数据,形成了一条watch链。这种代码很难维护,而且容易出问题。大部分情况下,用computed可以避免这种情况。

道理五:状态管理要适度

Vue 3的状态管理,从Vuex到Pinia,一直在演进。

我一开始用Vuex,后来转用Pinia。Pinia确实比Vuex简洁很多,类型支持也更好。但我发现,很多人(包括我自己)有一个误区:什么状态都往全局状态管理里放。

结果就是,store里塞满了各种状态,有些根本不需要全局共享。这不仅增加了状态管理的复杂度,也让组件和store之间的耦合变得很严重。

我的建议是:

  • 只有需要在多个组件之间共享的状态,才放到全局store里
  • 组件内部使用的状态,就放在组件内部
  • 跨层级但不需要全局共享的状态,可以用provide/inject
  • 表单状态、临时状态,尽量放在组件内部

还有一个问题是,很多人把业务逻辑也放到了store里。store应该只负责状态管理,业务逻辑应该放在单独的服务层或者组合式函数里。这样代码的职责更清晰,也更容易测试和复用。

Pinia是一个很好的状态管理工具,但它不是万能的。合理地划分状态的范围,才是最重要的。

道理六:组件拆分要合理

组件化是Vue的核心思想之一,但组件拆分不是越细越好。

我一开始的习惯是,把页面拆成很多小组件,觉得这样更"组件化"。结果就是,一个页面有十几个组件,组件之间的通信很复杂,props和emit层层传递,维护起来很痛苦。

后来我慢慢明白,组件拆分的原则应该是:

  • 可复用的部分,拆成组件
  • 逻辑独立的部分,拆成组件
  • 太大的组件(超过几百行),考虑拆分
  • 不要为了拆分而拆分

还有一个常见的问题是,组件的props设计得太复杂。一个组件有十几个props,各种配置项,用起来很麻烦,维护起来也很困难。

我的建议是:

  • 组件的props尽量少,只保留必要的
  • 复杂的配置可以用v-model或者插槽来处理
  • 组件之间的通信,优先用props和emit,不要动不动就用全局状态
  • 合理使用插槽,让组件更灵活

组件拆分是一门艺术,不是科学。没有绝对的标准,需要根据项目的实际情况来判断。但基本原则是:高内聚、低耦合。

道理七:性能优化要有的放矢

Vue 3的性能已经很好了,但在大型项目中,性能优化还是很重要的。

我一开始做性能优化的时候,很盲目。看到什么优化技巧都想用,v-once、v-memo、shallowRef、keep-alive,能用的都用上。但后来发现,很多优化根本没有效果,反而增加了代码的复杂度。

性能优化的正确姿势是:先测量,再优化。

用Vue DevTools或者浏览器的性能分析工具,找出真正的性能瓶颈。然后针对瓶颈做优化,而不是盲目地使用各种优化技巧。

常见的性能优化手段包括:

  • 用v-for的时候加上key,而且不要用index作为key
  • 大列表用虚拟滚动,不要一次性渲染所有数据
  • 合理使用v-once和v-memo,减少不必要的重新渲染
  • 用shallowRef和shallowReactive,减少深度响应式的开销
  • 用defineAsyncComponent异步加载组件,减少首屏加载时间
  • 用keep-alive缓存组件状态,避免重复渲染
  • 合理使用computed的缓存特性

但这些优化手段,都要在确实有性能问题的时候才用。如果项目本身性能很好,就不需要过度优化。过早优化是万恶之源,这句话在Vue项目中同样适用。

道理八:TypeScript很重要

Vue 3对TypeScript的支持非常好,这也是Vue 3的一大优势。

我一开始用Vue 3的时候,还是用JavaScript。觉得TypeScript太麻烦,要写类型,影响开发效率。但后来项目大了之后,JavaScript的问题就暴露出来了:

  • 重构的时候很害怕,不知道改了一个地方会不会影响其他地方
  • 函数的参数和返回值不明确,调用的时候容易出错
  • IDE的智能提示不够好,很多时候要靠记忆
  • 团队协作的时候,代码的可读性和可维护性差

后来我转用了TypeScript,一开始确实觉得麻烦,很多地方要写类型,有时候类型报错还不知道怎么解决。但用了一段时间之后,就离不开了。

TypeScript的好处是:

  • 类型安全,很多错误在编译阶段就能发现
  • IDE的智能提示更好,开发效率反而提高了
  • 重构更安全,有类型检查做保障
  • 代码的可读性和可维护性更好
  • 团队协作更顺畅

我的建议是,只要项目不是特别小,都应该用TypeScript。Vue 3 + TypeScript + Volar,开发体验非常好。

写在最后

用了三年Vue 3.4+,最大的感受是:Vue 3是一个非常优秀的框架,但它不是银弹。

框架只是工具,重要的是使用工具的人。理解框架的设计思想,掌握最佳实践,根据项目的实际情况做出合理的选择,这才是最重要的。

这些道理,有些是我从文档中学到的,有些是从别人的经验中学到的,但更多的是自己踩坑踩出来的。希望这篇文章能帮你少踩一些坑。

技术在不断发展,Vue也在不断更新。保持学习,保持实践,才能跟上技术的步伐。