3. 组件化设计思想
在 QML 界面架构中,组件化与模块化经常被混用,但二者关注的层次并不相同。理解它们的差异,是建立可扩展 QML 工程的第一步。组件化关注“界面单元如何封装与复用”,模块化关注“工程单元如何划分与依赖”。在 QML 中,一个 Button.qml 是组件,而一个包含按钮、弹窗、主题、工具函数的 ui-core 目录则更接近模块。
一、组件化与模块化的本质区别
组件化是以可复用 UI 单元为中心的拆分方式。它强调一个 QML 文件或一组 QML 文件对外暴露清晰的属性、信号、方法和默认插槽,内部实现可以替换而不影响调用方。模块化则是以工程边界为中心的拆分方式,它通过目录、qmldir、命名空间、构建目标或包管理来组织一组相关组件与资源。
换句话说,组件化解决“这个按钮能不能在十个页面里复用”,模块化解决“这十个页面和按钮应该放在哪个工程目录、由谁依赖、如何发布”。二者不是替代关系,而是不同粒度的协作关系:模块通常由多个组件构成,组件通过模块获得稳定的导入路径与版本边界。
二、高内聚低耦合原则
高内聚指一个组件内部只做一件事,并且把这件事做完整。例如 SearchBar.qml 只负责输入、清空、提交搜索信号,不负责请求网络,也不负责跳转页面。低耦合指组件之间尽量通过属性、信号和接口通信,而不是直接访问彼此的 id 或内部对象。
在 QML 中,低耦合最常见的做法是:父组件通过属性向下传数据,子组件通过信号向上通知事件。避免在子组件中写 parent.parent.color = "red" 这类依赖外部结构的代码,因为一旦布局层级变化,组件就会失效。
// 推荐:通过属性与信号解耦
// SearchBar.qml
Item {
id: root
property string placeholder: "搜索"
signal submitted(string keyword)
TextField {
id: input
placeholderText: root.placeholder
}
Button {
text: "搜索"
onClicked: root.submitted(input.text)
}
}
// 调用方只关心接口,不关心内部结构
SearchBar {
placeholder: "搜索用户"
onSubmit: userModel.search(keyword)
}
三、可复用性与可维护性目标
可复用性不是“写一次到处粘贴”,而是“写一次,通过参数适配不同场景”。一个可复用组件通常具备以下特征:属性可配置、信号可扩展、样式可覆盖、默认行为合理、不依赖具体业务数据。可维护性则要求组件边界清晰、命名一致、依赖方向单一、修改影响范围可控。
| 维度 | 组件化关注点 | 模块化关注点 | 可复用/可维护目标 |
|---|---|---|---|
| 拆分单位 | QML 文件或组件组 | 目录、qmldir、包 | 按职责拆分,避免上帝组件 |
| 对外接口 | 属性、信号、方法、插槽 | import 路径、版本、命名空间 | 接口稳定,内部可替换 |
| 依赖方向 | 父到子传数据,子到父发信号 | 页面依赖模块,模块不依赖页面 | 单向依赖,避免循环引用 |
| 复用方式 | 参数化配置、样式覆盖 | 跨工程导入、版本发布 | 一次实现,多场景适配 |
| 维护成本 | 修改内部不影响调用方 | 模块升级有边界与记录 | 影响范围可预测 |
四、案例场景:从“复制粘贴”到“组件复用”
假设一个 QML 项目中有三个页面都需要用户卡片:登录页显示当前用户,列表页显示用户摘要,设置页显示账户信息。初期做法是每个页面各写一份 Rectangle + Image + Text,结果头像圆角、字体大小、点击行为都不一致。重构时抽取 UserCard.qml,对外暴露 userName、avatarUrl、subtitle 和 clicked() 信号,三个页面通过属性传入不同数据,样式由主题模块统一控制。
重构收益:用户卡片从 3 份重复代码变为 1 个组件;修改头像圆角只需改一处;新增“访客卡片”只需传入不同属性,不需要复制文件。
五、常见误区与复盘
- 误区一:把“文件拆分”当成组件化。拆成很多 QML 文件,但每个文件仍然直接访问全局对象和外部 id,耦合并没有降低。
- 误区二:组件属性过多。一个组件暴露 20 多个属性,说明职责不单一,应该继续拆分或使用配置对象。
- 误区三:模块边界模糊。页面直接 import 另一个页面的私有组件,导致模块之间形成网状依赖,难以独立发布。
- 误区四:过度抽象。只有一处使用的界面也强行抽成组件,增加跳转成本,反而降低可读性。
实践建议:先按“是否会被复用”和“是否有独立职责”判断是否抽取组件;再按“是否会被其他工程或团队依赖”判断是否升级为模块。组件接口保持窄而稳定,模块依赖保持单向且可追踪。
组件化与模块化共同服务于同一个目标:让 QML 界面在需求变化时,修改范围可控、复用成本低、协作边界清晰。高内聚让每个单元自身完整,低耦合让单元之间互不拖累,可复用性与可维护性则是衡量拆分是否合理的最终标准。