写了六年Vue才敢说:组件库堆得越顺手,重构时欠的债越重
摘要:组件库的便利性掩盖了架构决策的懒惰,导致重构时技术债沉重。本文提出区分基础与业务组件、控制API表面积、用类型系统约束契约三条原则,帮助团队在享受组件库效率的同时,保持架构的清晰与可维护性。
标题不是噱头,而是一个写了六年Vue的开发者,在第三次大规模重构后最真实的感受。组件库用起来越顺手,重构时欠下的技术债就越重。这不是组件库本身的错,而是我们对待它的方式出了问题。今天,我想从技术演进的视角,聊聊这个几乎每个前端团队都会踩的坑。
过去两年,前端圈的关键词从“组件化”悄然转向了“设计系统”和“可维护性”。Vue 3的Composition API普及、Vite成为默认构建工具、TypeScript覆盖率成为团队硬指标——技术栈在升级,但很多团队的组件使用习惯还停留在“能跑就行”的阶段。组件库的繁荣,某种程度上掩盖了架构决策的懒惰。我们习惯了从现成的组件库里复制粘贴,习惯了用props和slots快速拼装页面,却很少问一句:这个组件的边界在哪里?它的状态该由谁管理?
组件库的“顺手”,本质上是抽象泄漏的延迟。当你调用一个表格组件,传入columns和data,它帮你处理了排序、分页、筛选,你感觉效率极高。但三个月后,产品要求行内编辑、跨页选择、自定义单元格渲染,你发现这个组件的API根本覆盖不了这些场景。于是你开始加props、加事件、加插槽,组件逐渐膨胀成一个“万能组件”。每一次需求变更,都是在原有抽象上打补丁,而不是重新思考模型。这种“顺手”的代价,是组件内部逻辑的熵增,以及外部调用方对隐式行为的依赖。
更隐蔽的债,是组件库带来的“一致性幻觉”。团队统一使用同一套组件库,看起来风格一致、交互一致,但一致性背后是业务逻辑的缺席。比如一个表单组件,它封装了校验规则、错误提示、提交状态,但不同业务对“必填”的理解可能不同,对“异步校验”的时机要求也不同。当这些差异被组件的默认行为掩盖时,开发者会倾向于修改组件来适配业务,而不是在业务层做适配。久而久之,组件库变成了一个承载所有业务特例的怪物,重构时牵一发而动全身。
Vue的响应式系统加剧了这个问题。ref和reactive的便利性,让状态管理变得随意。组件内部用ref定义状态,父组件通过props传递,子组件通过emit修改——这种模式在小型页面里很清晰,但在复杂页面里,状态流会变得难以追踪。组件库往往提供了v-model的语法糖,让双向绑定看起来优雅,但背后的数据流向是模糊的。重构时,你很难判断一个状态变更到底来自用户交互、父组件更新,还是组件内部的watch。这种隐式耦合,是组件库“顺手”带来的最大债务。
那么,如何避免组件库堆得越顺手、重构时欠的债越重?我的观点是:把组件库当作“词汇”,而不是“语法”。词汇可以复用,但语法必须由业务自己定义。具体来说,有三条原则值得坚持。
第一,区分“基础组件”和“业务组件”的边界。基础组件只负责渲染和交互,不承载业务逻辑;业务组件负责编排基础组件,并管理业务状态。很多团队的问题在于,把业务逻辑塞进了基础组件,导致基础组件无法复用,业务组件又过于臃肿。重构时,应该先梳理组件的职责,把业务逻辑从基础组件中剥离出来,用组合式函数(composable)来封装可复用的逻辑。
第二,控制组件的API表面积。一个组件的props和事件越多,它的使用成本就越低,但维护成本就越高。每次新增一个prop,都要问:这个prop是否可以通过组合现有prop实现?是否可以通过插槽暴露给调用方?是否应该由调用方在业务层处理?组件的API应该像函数签名一样,保持最小且稳定。重构时,优先收敛API,而不是继续扩展。
第三,用类型系统约束组件的使用方式。TypeScript在Vue 3中的支持已经非常成熟,但很多团队只把它当作“类型检查工具”,而没有用它来设计组件契约。通过泛型、联合类型、条件类型,可以精确描述组件的输入输出,让调用方在编译期就发现不合理的用法。重构时,类型系统是最好的回归测试——任何破坏契约的修改都会立刻暴露。
最后,我想说,组件库不是架构的替代品。它解决的是“重复造轮子”的问题,但解决不了“轮子该装在哪”的问题。写了六年Vue,我越来越觉得,重构的债不是组件库欠下的,而是我们在使用组件库时,放弃了思考架构的责任。组件库越顺手,越要警惕:我们是在用组件搭建系统,还是在用组件逃避设计?