Vue 3.4在2023年12月底发布了,带来了不少实用的新特性。我们项目最近从Vue 3.2升级到了3.4,顺便用新特性重构了一些老旧代码,效果很不错。
这篇文章就来分享一下Vue 3.4的新特性,以及如何用这些新特性把烂代码重构成优雅代码。
为什么要重构
先说说为什么要重构。
我们的项目是一个中后台管理系统,用Vue 3.2写的,已经维护了两年多。随着功能不断叠加,代码越来越臃肿,很多组件动辄上千行,逻辑混乱,难以维护。
具体的问题有几个:
第一,组件太大。很多组件把模板、逻辑、样式都写在一起,一个文件几千行,改一个功能要翻半天。
第二,逻辑重复。很多相似的逻辑在不同组件里重复实现,比如表单验证、表格分页、数据加载,每个组件都写一遍,维护起来很麻烦。
第三,响应式使用不规范。很多地方用了reactive包裹大对象,导致解构丢失响应式,或者用了toRefs但用得不对,导致性能问题。
第四,类型定义不完整。虽然用了TypeScript,但很多地方用了any,类型检查形同虚设。
Vue 3.4的新特性,正好可以解决这些问题。
defineModel
第一个重要的新特性是defineModel,这是Vue 3.4正式稳定的功能。
以前在Vue里,子组件要和父组件做双向绑定,需要定义props和emits,然后在子组件里监听props变化,通过emit通知父组件。代码写起来很啰嗦。
比如以前的写法是这样的:
// 子组件 const props = defineProps({ modelValue: { type: String, required: true } })
const emit = defineEmits(['update:modelValue'])
function updateValue(val) { emit('update:modelValue', val) }
模板里用的时候还要手动绑定value和监听input事件,很麻烦。
有了defineModel之后,写法简化了很多:
const model = defineModel()
// 直接用 model.value 读写 model.value = '新值'
模板里直接用v-model绑定到model就行,不用再手动处理props和emit。
我们项目里有很多自定义表单组件,比如自定义输入框、选择器、日期选择器,以前每个都要写一堆props和emit的代码。用defineModel重构之后,每个组件少写了几十行代码,逻辑也清晰了很多。
而且defineModel还支持多个v-model,比如一个组件可以同时有v-model:start和v-model:end,用来绑定开始和结束日期。以前要实现这个功能,需要定义两对props和emit,现在用defineModel('start')和defineModel('end')就行,简洁很多。
响应式语法糖改进
Vue 3.4对响应式语法糖做了改进,特别是reactive的解构问题。
以前用reactive定义一个对象,解构之后会丢失响应式:
const state = reactive({ count: 0, name: '张三' })
let { count, name } = state // 这样解构之后,count和name就不是响应式的了 // 修改count不会触发更新
以前的解决办法是用toRefs:
const { count, name } = toRefs(state) // 这样count和name是ref,保持响应式
但toRefs用起来也有不便,比如解构出来的是ref,用的时候要加.value,模板里不用加但script里要加,容易搞混。
Vue 3.4引入了reactive props的解构,在defineProps里可以直接解构,保持响应式:
const { count, name } = defineProps<{ count: number name: string }>()
// 这样解构出来的count和name是响应式的 // 不用toRefs了
这个改进在组件里特别实用,以前defineProps之后还要用toRefs才能解构,现在直接解构就行,代码简洁了很多。
我们项目里很多组件都用了这个特性,把以前的toRefs都去掉了,代码更简洁,也不容易出错。
useTemplateRef
Vue 3.4新增了useTemplateRef,用来获取模板引用,比以前的ref方式更类型安全。
以前获取模板引用是这样的:
const inputRef = ref<HTMLInputElement | null>(null)
// 模板里 <input ref="inputRef" /> // 用的时候 inputRef.value?.focus()
这种方式的问题是,ref的名字和模板里的ref属性名字要手动对应,容易写错。而且类型是HTMLInputElement | null,用的时候每次都要判空。
Vue 3.4的useTemplateRef写法:
const inputRef = useTemplateRef<HTMLInputElement>('input')
// 模板里 <input ref="input" /> // 用的时候 inputRef.value?.focus()
useTemplateRef的好处是,参数就是模板里ref的名字,类型更明确。而且Vue 3.4之后,useTemplateRef返回的ref在组件挂载之后一定有值,不用再担心null的问题(虽然类型上还是可能null,但运行时更可靠)。
我们项目里把所有的模板引用都改成了useTemplateRef,代码更规范,类型检查也更严格了。
性能优化
Vue 3.4在性能上也有不少优化,我们重构的时候也利用了这些优化。
第一个优化是重新渲染的效率提升。Vue 3.4优化了虚拟DOM的diff算法,特别是在有很多静态节点的时候,渲染速度更快。我们项目里有很多表格组件,以前大数据量的时候渲染有点卡,升级之后明显流畅了。
第二个优化是水合(hydration)的优化。Vue 3.4对SSR水合做了优化,特别是有v-if和v-for的地方,水合速度更快,错误提示也更清晰。我们项目有SSR的需求,升级之后首屏加载速度提升了不少。
第三个优化是watch的优化。Vue 3.4对watch做了优化,特别是监听多个源的时候,回调触发更准确,不会出现以前的重复触发或者漏触发的问题。
我们重构的时候,把一些性能瓶颈的组件做了优化,比如用v-memo缓存静态部分,用shallowRef减少深层响应式的开销,用computed缓存计算结果。配合Vue 3.4的性能优化,整体性能提升了大概20%。
代码组织优化
除了新特性,重构的时候我们也优化了代码组织。
第一个是组合式函数(Composables)的使用。我们把很多重复的逻辑抽成了组合式函数,比如useForm、useTable、usePagination、useLoading等。每个组合式函数负责一块逻辑,组件里只需要调用,不用写重复代码。
比如useForm:
function useForm(initialValues, validateRules) { const form = reactive({ ...initialValues }) const errors = reactive({})
function validate() { // 验证逻辑 }
function reset() { Object.assign(form, initialValues) }
return { form, errors, validate, reset } }
组件里用的时候:
const { form, errors, validate, reset } = useForm( { name: '', email: '' }, { name: required, email: [required, email] } )
这样表单相关的逻辑都封装在useForm里,组件里只需要关注业务逻辑,代码清晰很多。
第二个是按功能组织文件。以前我们是按类型组织文件,比如components、views、utils、stores。现在改成按功能模块组织,比如user模块里有UserList.vue、UserForm.vue、useUser.ts、userApi.ts。这样一个功能的所有文件都在一个目录里,找起来方便,维护起来也方便。
第三个是类型定义的完善。我们把所有的any都去掉了,给每个变量、每个函数参数、每个返回值都加了类型。还定义了很多业务类型,比如User、Order、Product,用interface和type统一定义,避免重复定义。
重构的经验
重构了几个月,总结了一些经验。
第一,不要为了用新特性而用新特性。新特性是为了解决问题的,如果旧代码没有问题,就不用改。只有当新特性能明显提升代码质量或者开发效率的时候,才值得重构。
第二,逐步重构,不要一次性全改。我们是一个模块一个模块地重构,改完一个测试一个,确保没有问题再改下一个。一次性全改风险太大,容易出问题。
第三,重构的时候要写测试。重构最担心的是改出bug,所以要有测试保障。我们给核心逻辑写了单元测试,给关键页面写了E2E测试,重构之后跑一遍测试,确保功能正常。
第四,团队要达成共识。重构不是一个人的事,需要整个团队都认同新的写法和规范。我们制定了代码规范,组织了代码评审,确保大家写出来的代码风格一致。
第五,关注Vue的更新。Vue的迭代很快,每个版本都有新特性和优化。要关注官方的更新日志,了解新特性,及时用到项目里。
写在最后
Vue 3.4的新特性,配合合理的代码组织和规范,能让代码质量提升一个档次。defineModel简化了双向绑定,响应式解构简化了props使用,useTemplateRef让模板引用更规范,性能优化让应用更流畅。
从烂代码到优雅代码,不是一蹴而就的,需要持续的重构和优化。Vue 3.4给了我们更好的工具,让重构变得更容易。
如果你还在用Vue 3.2或者更早的版本,建议升级到3.4,体验一下新特性。如果你已经在用3.4,不妨看看项目里有没有可以用新特性优化的地方,说不定能让代码优雅很多。
希望这篇文章能给你一些启发。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录