面向大型组件库的 Vue 3:Composition API 与 Options API 对比
Vue 3 Composition API 与 Options API:在大型后台组件库中,它们如何影响复用、测试与新成员上手速度。

在大型后台组件库中这个选择为何重要
大型管理后台的组件库不是一个带几个按钮的营销站点。它包含数十(甚至上百)个会在各个界面重复出现的构建块:支持排序和批量操作的数据表、筛选面板、带验证规则的表单、抽屉和模态框、确认流程,以及像日期选择器和权限守卫这样的工具组件。
因为这些模式无处不在,团队经常复制并微调代码以赶期限。一个表格有自定义筛选栏,另一个有稍微不同的实现,很快你就会有五个“几乎相同”的版本。到那时,Composition API 与 Options API 的选择就不再只是个人偏好,而会影响整个库的健康状况。
通常第一个坏掉的是一致性。UI 仍能工作,但行为会漂移:某处按 Esc 能关闭模态,而另一处不能;同一个字段在一个页面上是 blur 校验,而在另一个页面上是 submit 时校验。接着,速度下降,因为每次改动都需要在近似重复的实现中找不同。最后是信心下降:大家避免重构,因为无法预判会影响到什么。
在共享库里,三个实际决策最重要:
- 代码复用:如何在不产生纠缠依赖的前提下打包共享逻辑。
- 测试:在不依赖脆弱 UI 测试的情况下验证行为的难易程度。
- 入门:新贡献者多快能读懂一个组件并做出安全修改。
举个简单例子:你的后台有 20 个列表页,产品团队要求新增“已保存筛选”。如果表格、筛选和 URL 同步逻辑分散且不一致,你要么发布很慢,要么发布有缺陷。你选择的 API 风格决定了这些逻辑是否会集中在一个可复用的地方、它如何清晰地与每个页面连接,以及新手能否轻松扩展它。
如果你正在构建 Vue 3 管理应用(包括在像 AppMaster 这样的平臺里使用 Vue 3 作为 Web UI 层的团队),尽早做出这个选择可以在未来节省数月的维护成本。
Options 和 Composition 在日常代码中的不同
最快感受差别的方法是打开一个大型后台组件,问自己:“我在哪里改变这个特性的行为?”在组件库中,这个问题每天都会出现。
使用 Options API 时,代码按类型分组:data 放状态,methods 放动作,computed 放派生值,watch 放副作用。当组件小的时候,这种结构很容易浏览。但在大型表格或表单组件中,一个功能(比如批量操作或字段验证)的逻辑经常分散在多个区块。你可以保持整洁,但需要纪律和一致的命名来避免“在文件里到处跳”的工作流。
使用 Composition API 时,代码通常按功能分组。你把相关的状态、派生值、副作用和辅助函数写在一起,并能把重复逻辑抽成 composables。在后台风格的库里,这通常更符合人们的思路:"关于筛选的一切都在这里", "关于行选择的一切都在这里"。它也能减少不同组件间的重复,例如在多个表格间复用 usePagination。
一个日常区别是依赖如何显现。
- Options API 感觉更隐式:一个方法可能依赖
this.user、this.filters和this.loading,你只有读到方法内部才知道。 - Composition API 倾向更明确:当一个函数闭包中使用了
filters和loading,你能在附近看到这些变量的定义,并在需要时把它们作为参数传给辅助函数。
权衡是:如果把所有东西都塞进一个没有结构的 setup(),Composition API 会变得嘈杂。
一个实践经验的启发规则:
- 当组件主要是展示型且逻辑轻量时,选择 Options API。
- 当组件包含多个特性且这些特性在库中有共享规则时,选择 Composition API。
- 若选用 Composition API,约定一个简单的布局,把代码按功能分组(而不是“先放所有 refs”)。
- 若选用 Options API,强制命名并用简短注释和一致的方法名把相关逻辑放在一起。
两者都能工作。关键是选一种让下一次改动看起来明显而非“聪明”的组织方式。
代码复用:什么能干净扩展,什么会变得混乱
在后台风格的应用中,复用不是锦上添花。你会在数十个页面重复相同行为,细小的不一致就会变成 bug 和支持工单。
大多数复用需求集中在几个反复出现的类别:与后端匹配的排序/筛选/分页、表单验证和错误映射、权限校验与 UI 限制、查询同步(URL 参数、已保存视图、默认筛选)以及带有表格选择规则的批量操作。
Options API 的复用:强大,但容易隐藏复杂性
在 Options API 中,复用经常从 mixins、extends 或插件开始。
Mixins 快速但扩展性差,因为它们会隐藏方法或计算属性来自哪里。两个 mixin 可能在同名方法上悄悄冲突,此时你在调试时看不到组件文件中发生了什么。
extends 比 mixins 看起来干净一些,但它仍会带来继承谜题,你不得不读多个文件才能理解组件实际做了什么。插件适合应用级关注点(全局指令、共享服务),但不适合包含随屏幕变化的业务规则。
问题通常在于复用变得隐式。新贡献者无法在不搜索整个代码库的情况下回答“这个数据来自哪里?”
Composition API 的复用:composables 保持显式
Composition API 的复用通常围绕 composables:返回 refs、computed 值和处理函数的小函数。大赢点是复用会在组件顶部附近显现,你可以传入参数而不是依赖隐藏的组件上下文。
例如,usePagination 可以接受默认值并以一致的形状发出变化,而 usePermissions 可以接受当前角色和功能名。到那时,选择更多变成库是偏好显式连接还是隐式继承的问题。
为了保持复用可预测,把每个可复用单元当作一个小 API:给它清晰的名字,定义输入和输出,并保持单一职责。如果一个 composable 开始同时处理分页、缓存、权限和通知,就把它拆分。这样以后替换单个部分不会破坏其他东西。
构建可复用的表单和表格而不头疼
在后台风格的应用中,表单和表格是组件库要么物超所值要么变成迷宫的地方。两种 API 都能工作,不同之处在于你如何打包像脏状态、错误映射和提交流程这样的共享行为,而又不让每个组件显得“特殊”。
对于共享表单逻辑,Options API 通常会把你推向 mixins 或共享 helper。mixins 刚开始方便,但后来常常难以回答基本问题:"这个字段的错误从何而来?" 或 "为什么提交被禁用?"
Composition API 使这类复用更可见,因为你可以把逻辑移到 composables(例如 useDirtyState、useFormErrors、useSubmitFlow),并确切地看到一个表单组件引入了哪些内容。在大型库中,这种清晰度通常比节省几行代码更重要。
保持组件 API 稳定的实用做法是把公共表面当作契约:props、emits 和 slots 应该很少改变,即便你重写内部实现。这个契约在两种风格中看起来一样,但 Composition API 常让重构更安全,因为你可以一次替换一个 composable,而不去动模板 API。
一些随着库增长仍然保持清晰的模式:
- 构建只做一件事的基础组件(BaseInput、BaseSelect、BaseTable),然后把它们组合成功能组件。
- 对布局灵活性使用 slots(操作区、空状态、单元格渲染),而不是为每个边缘情况增加 props。
- 及早规范事件(例如
update:modelValue、submit、rowClick),以免应用依赖内部细节。 - 把验证和格式化放在输入附近,但把业务规则放在外面(composables 或父容器)。
过度抽象是常见陷阱。一个处理所有字段类型、所有验证规则和所有布局选项的“超级表单”往往比普通 Vue 更难用。一个简单的规则是:如果一个基础组件需要超过少数几个 props 才能覆盖所有团队需求,它可能应该拆成两个组件。
有时重复是正确的选择。如果只有一个页面需要带有多行分组的奇怪表头,复制一小段并把它保留为本地实现更好。巧妙的抽象通常带来长长的维护尾巴,特别是当新贡献者加入并试图理解“普通”组件与框架内框架之间的差别时。
如果你在为大型表单和表格库在 Composition 与 Options 之间做选择,优先优化数据流的可读性。复用很好,但当它隐藏了从用户操作到发出事件的路径时,就不值得。
测试影响:什么更容易验证
在组件库中,测试通常分为三类:纯逻辑(格式化、验证、过滤)、渲染(在给定状态下显示什么)和交互(点击、输入、键盘、发出事件)。你选择的 API 风格会改变你能在不挂载完整组件的情况下测试第一类的频率。
Options API 的测试通常看起来像“挂载组件,操控实例状态,断言 DOM”。这可行,但它会促使更大的测试,因为逻辑混在 methods、computed、watch 和生命周期钩子中。当出错时,你还要花时间判断是 watcher 的时序、生命周期副作用还是逻辑本身的问题。
Options API 在以下场景下常显得直接:
- 依赖生命周期顺序的用户流程(在 mount 时 fetch、在路由变化时重置)
- 由 watcher 驱动的行为(自动保存、查询同步)
- 从组件方法发出的事件(
save()、reset()、applyFilter())
Composition API 改变了平衡。如果你把逻辑移到 composables,就可以像普通函数一样对其做单元测试,使用小的输入和清晰的输出。这减少了你需要的“挂载并点击”类测试,并让失败更局部。它也让依赖更容易控制:你可以把依赖(比如 fetch 函数、日期格式化器或权限检查器)作为参数传入 composable,而不是去 mock 一个全局。
一个具体的例子:一个带排序、分页和选中行的可复用 AdminTable。在 Composition API 下,选择逻辑可以放在 useRowSelection() 中,并在不渲染表格的情况下测试(切换、清除、全选、在页面间保留)。然后你只需少量组件测试来确认模板正确地绑定了按钮、复选框和发出的事件。
为保持测试小而可读(无论哪种风格),在逻辑与 UI 之间建立清晰的分界:
- 把业务规则放在纯函数或 composables 中,而不是放在 watchers。
- 把副作用(fetch、存储、定时器)放在可注入的依赖之后。
- 每个组件保持少量聚焦的集成测试,而不是一个巨大的“所有功能”测试。
- 在库中一致命名状态和事件(减少测试设置)。
- 避免隐藏耦合(比如方法 A 依赖 watcher B 运行)。
如果你的目标是通过风格决策提高测试稳定性,那就倾向于减少依赖生命周期的行为,把逻辑单元化,这样你可以在不依赖 DOM 的情况下验证它们。
新贡献者入门:人们多快能成为产能状态
在大型后台组件库中,入门更多是教人们在哪里找到东西、如何遵循相同约定,以及如何有把握地修改。大多数减速来自三个差距:导航(逻辑在哪里?)、约定(我们这里如何做?)和信心(如何在不破坏五个页面的情况下修改?)。
使用 Options API,新人通常在第一天更容易上手,因为结构熟悉:props、data、computed、methods、watchers。代价是实际行为常常分散。像“服务器端筛选”这样的单个特性可能被拆到 watcher、computed 和两个 methods 中,再加上 mixin。人们可以阅读每个区块,但需要时间把故事拼起来。
使用 Composition API,入门的优势是相关逻辑可以放在一起:状态、副作用和辅助函数在一处。代价是需要学习 composable 的使用模式。新贡献者需要理解像 useTableState() 这样的模式,以及响应式值如何在多个 composables 之间流动。如果没有清晰边界,可能会觉得是在没有地图的文件间跳来跳去。
一些约定能消除大多数问题,无论你选哪种风格:
- 使用可预测的目录结构:
components/、composables/、types/、tests/。 - 选定命名模式并坚持(例如:
useX、XTable、XForm)。 - 添加简短的 docblock:组件做什么、关键 props、主要事件。
- 定义一个“应急规则”(何时可以添加新 composable 或 helper)。
- 保留一个小的“金牌”组件示例,展示首选模式。
举例:如果你团队使用 Vue 3 生成一个管理面板然后定制它(例如用 AppMaster 生成 Web 应用并由开发者扩展),当有一个明确的地方去调整表格行为(排序、筛选、分页)和一个明确的地方去调整 UI 连接(slots、列渲染器、行操作)时,入门情况会大幅改善。这种清晰性比你选择的 API 更重要。
逐步实施:如何选择风格并安全引入
对于大型管理 UI 库,最安全的办法是从一个边界明确的特性开始,把它当作试点,而不是整体重写。
挑一个复用高且行为清晰的模块,比如表格筛选或表单验证。修改代码前,写下它目前的行为:输入(props、query params、用户操作)、输出(事件、emits、URL 变化)和边界情况(空状态、重置、服务器错误)。
接着,设定边界。决定哪些必须保留在组件内部(渲染、DOM 事件、无障碍细节),哪些可以移到共享代码(解析筛选、去抖、构建 API 参数、默认状态)。库常出错的地方在于把 UI 决策移动到共享代码里,这会降低复用性。
一个实际的推广计划:
- 选一个能清楚展示该模式且被多个页面使用的组件。
- 抽取一个共享单元(composable 或纯 helper),保持 API 小且明确。
- 先为该单元添加一个基于真实后台场景的聚焦测试。
- 用新单元端到端重构所选组件。
- 把相同模式应用到另一个组件以确认其可扩展性。
保持共享 API 普通且显而易见。例如,一个 useTableFilters() composable 可以接受初始筛选并暴露 filters、apply()、reset() 和 toRequestParams()。避免魔法行为(例如自动读取全局状态),除非那是你们应用的既定规则。
试点后,发布一份简短的内部指南并附上一个示例供贡献者复制。比起长篇文档,一个具体规则更有效,比如:“所有表格筛选逻辑都放在 composable;组件只绑定 UI 控件并调用 apply()。”
在扩展前,使用简单的完成定义:
- 新代码在两个不同组件中读起来一致。
- 测试在不挂载完整 UI 的情况下覆盖共享逻辑。
- 新贡献者可以在不接触无关文件的情况下更改筛选规则。
如果你团队也用像 AppMaster 这样的无代码工具生成管理门户,同样的试点思路也适用:挑一个工作流(比如审批),定义行为,然后在大规模推广前标准化模式。
大型库中常见的错误与陷阱
大型组件库最大的问题很少是语法层面的。它们来自一系列局部决定的堆积,使得复用、测试和维护变得更难。
一个常见陷阱是随机混用模式。如果库一半使用 Options API,另一半使用 Composition API 且没有规则,每个新组件就会变成风格争论。你还会看到为相同问题出现重复而形状不同的解决方案(表单、表格、权限)。如果允许两种风格并存,写一条明确策略:新组件使用一种风格、遗留代码仅在需要时触碰、共享逻辑放在统一位置。
另一个陷阱是“上帝 composable”。它通常起始于有用的 useAdminPage() 或 useTable(),然后慢慢吸收路由、fetch、缓存、选择、对话框、通知和权限。它变得难以测试,因为一次调用触发许多副作用;也难以复用,因为每个页面只需要其中 30%,却得承担 100% 的复杂性成本。
Watchers 也是痛点来源。它们容易添加来修复不同步,但随之而来的时序 bug(尤其在异步数据和去抖输入时)很难复现。当有人报告“它有时会清除我的选择”时,你可能要花数小时复现并定位问题。
通常预警信号包括:
- 组件只能在某个特定 props/事件顺序下工作。
- composable 在不显式说明的情况下读写全局状态。
- 多个 watcher 更新同一片状态。
- 重构持续以小幅方式破坏消费者页面。
- 贡献者回避修改“那个文件”,因为风险太大。
最后一个陷阱是在重构时破坏公共 API。表格、筛选和字段组件会迅速传播。重命名一个 prop、改动发出的事件或调整 slot 行为都可能静默地破坏几十个页面。
更安全的做法是把组件 API 当作契约:弃用而不是删除,保留兼容层一段时间,并添加简单的用法测试,按消费者的真实使用方式挂载组件。如果你用 AppMaster 生成 Vue 3 管理界面,这点更重要,因为一致的组件契约能让屏幕复用更容易、变化更可预测。
提交到模式前的快速检查
在你最终选择 Composition、Options 或混合之前,对库中真实组件做几项快速检查。目标简单:让人容易找到逻辑、安全复用并能测试管理员真正依赖的部分。
1) 某人能快速找到逻辑吗?
打开一个典型的后台重型组件(筛选 + 表格 + 权限 + 批量操作)。假装你是新来者。
好的信号是贡献者能在 2 分钟内回答“筛选逻辑在哪里?”或“什么决定按钮是否禁用?”。在 Options API 中,这通常意味着逻辑在 computed、methods 和 watcher 中清楚分布。使用 Composition API 时,这意味着 setup() 被组织成小的命名块(或 composables),而不是一个巨大的函数。
2) 共享工具是像函数一样工作,还是带魔法?
无论你选哪种模式,共享代码都应该有清晰的输入输出,且副作用最小。如果 helper 去读取全局状态、修改传入对象或在不明显的情况下触发网络请求,复用就很危险。
快速检查:
- 你能从 composable 或 helper 的签名猜到它返回什么吗?
- 你能在两个组件中使用它而无需额外隐藏设置吗?
- 你能在测试中重置它的状态而不借助 hack 吗?
3) 你的测试是否聚焦管理员行为?
后台应用常在可预测的方式失败:筛选应用错误、权限泄露、表单校验不一致、编辑后表格状态异常。
别去测试内部实现细节(比如 watcher vs refs),而是围绕行为写测试:"给定角色 X,操作 Y 隐藏"、"保存失败显示错误并保留用户输入"、"筛选改变会更新查询并触发空状态消息"。这样即便后来在风格间重构,测试仍然稳定。
4) 你有统一的异步状态规范吗?
大型库里会出现许多小的异步流程:加载选项、校验字段、获取表格行、失败重试。如果每个组件都自己发明 loading/error 处理,入门和调试都会变慢。
选定一种清晰的异步状态形状(loading、error、重试、取消)。Composition API 常鼓励用可复用的 useAsyncX() composable,而 Options API 可以标准化 data() 状态加共享方法。无论哪种,只要整个库一致即可。
5) 组件公共 API 是否稳定且自说明?
把组件当作产品。它们的 props、events 和 slots 是契约。如果契约经常变化,每个后台页面都会变得脆弱。
寻找那种解释意图的注释(而非机械说明):props 的含义、保证发出的事件、哪些被视为内部。如果你用 AppMaster 生成内部管理工具,这种心态同样适用:稳定的构建块能让未来页面更快交付。
示例场景与给团队的下一步建议
想象你在重建一个后台的“Users”页面:一个筛选栏(状态、角色、创建日期)、一个可选中行的表格、批量操作(禁用、删除、导出)以及基于角色的访问控制(只有管理员能批量删除,经理能编辑角色)。
无论 Composition API 还是 Options API,UI 最终可以一样,但代码往往会组织不同。
在 Options API 中,你常得到一个大型组件,data 放筛选和选择,computed 放派生状态,methods 放获取、批量操作和权限检查。复用通常以 mixins 或共享 helper 的形式出现。结构熟悉,但相关逻辑可能分散(fetch 在 methods、查询同步在 watchers、权限在 computed)。
在 Composition API 中,你通常把页面拆成几个聚焦的 composables:一个负责查询和筛选,一个负责表格选择和批量操作,一个负责权限。页面组件成为这些部分的组装体,每个关注点的逻辑都聚合在一起。代价是你需要清晰的命名和文件夹约定,否则贡献者会觉得 setup 里有魔法。
复用通常在后台库中自然出现于以下方面:同步到 URL 的筛选、服务端表格模式(分页、排序、全选、批量操作保护)、权限检查与 UI 限制(按钮、列、行操作)以及跨页面一致的空/加载状态。
对大多数团队有效的下一步计划:
- 为新代码选定默认风格,只有书面理由才允许例外。
- 定义约定:composables 存放位置、命名方式、可导入范围与必须返回的内容。
- 添加一个小的参考页面(例如本示例的 Users 页面)作为首选模式的金标准。
- 先为可复用部分写测试(筛选、权限、批量操作),而不是视觉布局测试。
- 如果某些后台页面比深度定制更看重速度,考虑用无代码工具(比如 AppMaster)生成这些页面,然后把手写库用于真正独特的部分。
如果你已经使用 AppMaster,它能帮助你在生成部分与手写部分之间保持相同的心智模型:稳定的组件契约,以及以小且明确单元打包的共享逻辑。对于评估内部门户无代码方案的团队,AppMaster (appmaster.io) 能生成完整应用(后端、Web、移动端)同时仍允许你在 Vue 3 Web UI 层标准化关键部分。
如果这周只做一件事,把 Users 页面当作你的模板并在代码审查中强制执行。一个清晰的示例比长篇风格指南更能推动一致性。
常见问题
默认在你的库中使用 Composition API,如果你会频繁复用像筛选、分页、批量操作和权限控制这样的行为。它更便于把共享逻辑抽象成 composables,并让依赖关系更明确。当组件主要是展示型且逻辑很少时,使用 Options API 也很合适。
Options API 会按类型分组代码(data、methods、computed、watch),所以某个功能的逻辑常会分散在多个地方。Composition API 更常按功能分组,让“过滤”或“选择”相关的代码能放在一起。最佳选择是能让下一次改动容易定位且能安全应用的那种方式。
在 Options API 中,复用常从 mixins 或 extends 开始,这会隐藏方法和计算属性的来源并可能导致命名冲突。Composition API 倾向于用 composables,具有明确的输入输出,组件中能直接看到组合方式。对于共享库来说,显式的复用通常更易维护。
把每个 composable 当作一个小型 API:单一职责、明确参数与返回值。如果一个 composable 同时处理分页、缓存、权限和通知,就应该拆分。小型 composables 更容易测试、更易复用,也不容易引入意外副作用。
保持公共契约稳定:props、emits 和 slots 应尽量少改动。把输入格式化和基础校验放在字段组件附近,但把业务规则放在 composables 或容器组件中。这样即便重写内部实现,也不会迫使每个使用页面做改动。
Composition API 通常更方便在不挂载整个组件的情况下单元测试逻辑,因为你可以直接测试 composables 和纯函数。Options API 则更常需要挂载组件进行测试,watchers 和生命周期的时序可能会带来噪音。不论哪种风格,把业务规则与 UI 连接分离才是保证测试小而稳定的关键。
为异步状态(比如加载、错误、重试、取消)标准化一种形状,例如统一使用 loading、error,并定义清晰的重试或取消策略。不要让每个组件都发明自己的异步状态约定,否则调试会很慢。无论用哪种 API,重要的是在整个库中保持一致性。
Options API 对新手入门通常更友好,因为结构熟悉(props、data、computed、methods、watchers)。但真实逻辑往往分散,需要花时间把各块拼起来。Composition API 当贡献者熟悉 composables 和文件夹约定后,可以更快上手,因为相关行为被分组并且复用可见。最有用的做法是提供一个“golden”示例组件并在代码审查中强制执行相同模式。
选择一个边界清晰且高复用的特性(比如表格筛选或表单错误映射)作为试点。抽取一个小而明确的共享单元,先为该单元写专注的测试,然后将选中的组件端到端重构为使用该单元。该模式在至少两个组件上验证通过后,再逐步推广。
注意以下迹象:出现大量近似重复的实现;watchers 相互斗争;组件仅在特定 props/事件顺序下可用;或贡献者回避修改某些“危险”的文件。另一个警告是频繁破坏 props、事件或 slot 行为。遇到这些问题时,需要更明确的契约和更显式的复用策略。


