内容提要
Qt 6.12 为 QML Preview 引入热重载:保存文件后仅更新差异部分,保留对象与界面状态,无需重建整个场景。颜色、尺寸、绑定或函数体等简单改动可原地修补;结构性改动则重建组件,保留根对象与外部状态。若无法应用差异,则重启应用并回放录制的输入事件。该功能默认开启,可通过 Qt Creator 或 qmlpreview 使用,但在多线程 QML 引擎下不安全。
延伸解读
热重载的适用边界
热重载并非万能。文章明确指出,只有简单改动(如颜色、尺寸、绑定表达式或函数体)能原地修补;结构性改动(增删对象、属性、信号或绑定)会触发组件重建,保留根对象和外部状态;若无法应用差异(如根对象C++基类改变),则重启应用并回放输入事件。此外,多线程QML引擎下热重载不安全,可能引发数据竞争。
事件回放的局限与风险
事件回放用于在重启后恢复UI状态,但存在明显局限:若编辑移动了按钮位置,录制的点击可能落在错误目标上;动画和屏幕过渡可能导致点击落在错误的屏幕;后端数据变化或获取成本高也会影响回放效果。文章提醒,回放“在好日子里”也只能让你接近之前的状态,且若程序未准备好处理UI消失和重现,仍可能崩溃。
如何启用与配置热重载
热重载默认开启,可通过Qt Creator的Build -> QML Preview或命令行工具qmlpreview使用。若使用CMake,需确保应用启用QML调试(如定义QT_QML_DEBUG),并建议使用qt_add_qml_module。对于资源文件系统中的QML,需通过--resource选项映射到主机文件系统。可通过设置环境变量QMLPREVIEW_HOTRELOAD=0关闭热重载,以回退到旧行为进行对比。
当前限制与未来方向
文章承认这是首个版本,存在不少未完成之处:改变绑定种类(如字面量转为脚本绑定)会强制重建;C++基类改变无法原地修补;程序化创建的内层对象在重建时可能丢失。未来计划包括减少重建触发、支持更多原地修补,以及开发VS Code集成和让AI编码代理驱动预览循环。若遇到问题,建议提交包含前后QML文件和.qtd录制的bug报告。
Q&A
Qt 6.12 的热重载是什么?它和之前的 QML Preview 重载方式有什么不同?
Qt 6.12 的热重载是 QML Preview 的新功能,它通过比较新代码和运行中应用的差异,只更新变化的部分,保留对象和界面状态,而不是像以前那样删除所有对象并重新加载整个场景。
热重载在哪些情况下可以原地修补而不重建组件?
当差异只涉及现有绑定和函数体的修改时,可以原地修补。例如更改字面量(颜色、尺寸、边距、字符串、true/false)、更改绑定表达式、更改 JavaScript 函数或信号处理器的函数体,以及重新格式化、添加注释、移动空白等仅改变源代码位置的操作。
热重载如何处理结构性更改?
对于结构性更改(如添加或删除对象、属性、信号或绑定),热重载会重建整个组件。根对象会被重用并重新填充,但组件内 QML 创建的对象会重新创建。重建前,会保存从外部(C++ 或其他 QML 文档)覆盖的属性值、绑定和信号处理器,并在重建后恢复。
热重载在什么情况下无法应用差异,会采取什么措施?
当无法应用差异时(例如更改了最外层文档根对象的 C++ 基类型),调试服务会报告失败,客户端会重启应用程序并回放录制的输入事件,以恢复 UI 状态。使用 qmlpreview 时会显示错误信息,而 Qt Creator 会静默处理。
如何启用或禁用热重载?
热重载默认开启。要禁用它,可以在预览的应用程序环境中设置 QMLPREVIEW_HOTRELOAD=0。
使用热重载时有哪些已知限制或风险?
热重载在多线程 QML 引擎下不安全,因为原地修补会操作可能暴露给多个引擎的编译单元,类型引用没有数据竞争保护。此外,更改绑定类型、移动绑定、更改 C++ 基类型等操作会触发重建,可能丢失程序化创建的对象。
如何通过命令行使用 qmlpreview 进行热重载?
可以使用 qmlpreview 命令,例如 qmlpreview ./myapp。如果应用程序使用资源文件系统,需要传递 --resource 选项指定 .qrc 文件。使用 CMake 时,可以构建生成的 myapp_qmlpreview 目标来自动传递参数。
热重载中的事件回放是什么?它有什么作用?
事件回放是记录用户输入事件并在应用重启后快速回放,以恢复 UI 状态。它用于热重载无法应用差异导致重启时,将用户带回大致相同的界面位置。但回放可能因几何变化或时序问题而不精确。