2. QML基础回顾
在进入组件化界面架构设计之前,有必要把 QML 的核心机制重新梳理一遍。组件化并不是另起炉灶,而是把 QML 已有的语法、元素、绑定、信号与 JavaScript 能力组织成可复用、可组合、可维护的结构。理解这些基础概念之间的协作关系,才能判断哪些内容应该封装为组件、哪些应该通过属性暴露、哪些应该用信号解耦。
一、QML 语法与元素类型:声明式对象树
QML 是一种声明式语言,文件通常以 .qml 结尾。每个 QML 文件定义一个根元素,根元素内部可以嵌套子元素,形成一棵对象树。对象树不仅是视觉层级,也是生命周期与属性继承的载体。例如一个矩形内部放置文本和鼠标区域,父元素的尺寸变化可以影响子元素的布局,子元素也可以访问父级作用域中的 id。
import QtQuick 2.15
import QtQuick.Controls 2.15
Rectangle {
id: root
width: 320
height: 200
color: "#f5f7fa"
Text {
id: title
anchors.centerIn: parent
text: "QML 基础回顾"
font.pixelSize: 20
}
MouseArea {
anchors.fill: parent
onClicked: root.color = "#dbeafe"
}
}
QML 元素大致分为几类:可视化元素(如 Rectangle、Text、Image)、交互元素(如 MouseArea、Button)、布局元素(如 Row、Column、Grid、AnchorLayout)、模型视图元素(如 ListView、Repeater)以及非可视化元素(如 Timer、Connections、Binding)。组件化设计时,通常会把可视化与交互组合成自定义元素,把非可视化逻辑抽离为独立模块。
二、属性绑定:自动依赖追踪
属性绑定是 QML 最具表现力的特性之一。使用冒号 : 赋值时,如果右侧表达式引用了其他属性,QML 会自动建立依赖关系。当被依赖属性变化时,绑定表达式会重新求值。这种机制让界面状态与数据状态保持同步,而不需要手动编写更新代码。
Rectangle {
id: card
width: 200
height: width * 0.6
Text {
text: "宽度:" + card.width
anchors.centerIn: parent
}
}
绑定不是一次性赋值,而是一个持续存在的表达式。若后续用命令式赋值覆盖了绑定,例如 card.width = 300,原来的绑定关系会被破坏。组件化设计中,应尽量通过属性暴露和绑定传递来维持数据流,而不是在外部随意改写内部属性。
property alias 或只读属性,避免外部直接修改内部实现细节,从而保护绑定关系。三、信号与槽:事件驱动与解耦
QML 中的信号用于表达“发生了什么事”,槽则是响应信号的函数或表达式。元素自带信号,例如 MouseArea 的 clicked、Timer 的 triggered;也可以自定义信号,例如 signal submitted(string value)。信号与槽让子组件可以把内部事件向上传递,而不需要知道外部谁在监听。
Item {
id: editor
signal submitted(string content)
function submit() {
submitted("hello qml")
}
Component.onCompleted: submit()
}
Connections {
target: editor
function onSubmit(content) {
console.log("收到内容:", content)
}
}
在组件化架构中,信号是组件对外的“事件接口”。子组件只负责发出信号,父组件或控制器负责决定后续行为。这样组件之间不直接依赖,替换或复用组件时成本更低。
四、JavaScript 集成:逻辑表达与边界
QML 内嵌 JavaScript 引擎,可以在属性绑定、信号处理函数和自定义函数中编写 JavaScript。它适合做轻量计算、条件判断、字符串处理和局部状态转换。但不应把复杂业务逻辑全部塞进 QML,否则会降低可测试性与可维护性。
function formatSize(bytes) {
if (bytes < 1024) return bytes + " B"
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"
return (bytes / 1024 / 1024).toFixed(1) + " MB"
}
常见做法是:QML 负责视图与交互,JavaScript 负责视图逻辑,C++ 或后端负责业务与数据。组件化时,可以把通用 JavaScript 函数放入独立的 .js 文件,通过 import 引入,避免在多个组件中重复实现。
五、核心概念关系图
下面这张图把 QML 基础概念放在同一张关系图中:语法与元素构成对象树,属性绑定维持状态同步,信号与槽负责事件传递,JavaScript 提供逻辑表达,它们共同支撑组件化设计。
六、常见误区与复盘清单
| 误区 | 表现 | 改进方式 |
|---|---|---|
| 破坏属性绑定 | 外部直接给内部属性赋值,导致绑定失效 | 通过属性别名或只读属性暴露,内部维护绑定 |
| 信号滥用 | 所有交互都向上传递,父组件承担过多逻辑 | 区分局部状态与全局事件,局部状态留在组件内 |
| JavaScript 过重 | QML 中堆积大量业务计算与网络请求 | 把业务逻辑下沉到 C++ 或后端,QML 只做视图逻辑 |
| 元素层级过深 | 一个文件内嵌套十几层,难以定位与复用 | 按职责拆分为自定义组件,控制单文件复杂度 |
| id 命名随意 | id 冲突或语义不清,影响作用域与可读性 | 使用语义化 id,避免跨组件依赖内部 id |