Qt 与 React 混合开发
问题与探索
最近的项目遇到了一个问题:如何在 Qt 里去渲染 AI 对话并实现打字的效果。要在 Qt 里边实现相同的功能一种方案是首先用 C++ 对 Markdown 进行解析,然后将解析的 HTML 交由 Qt WebEngine 进行渲染。另外一种就是将解析、渲染的功能完全交由 Qt WebEngine 处理。
权衡之下,显然第二种方案更有优势,用 Web 处理 Markdown 和实现动画有很多成熟的解决方案。Ant Design X 提供了完整的解决方案,同时配合 markdown-it 也能处理 AI 返回的 Markdown。
根据需求,有两种方式让 Qt WebEngine 加载 HTML 文件,一种是将页面发布到服务器,从远程加载,另外一种是使用 Qt 的资源系统将 HTML 文件嵌入到生成的二进制可执行文件中。如果选择远程加载,下文中探讨的一些问题将不复存在,但是需要准备服务器等。这里以第二种方式进行探讨。
Web App 配置
完成了技术选型之后,接下来就要进行项目的开发了。要开始一个 React Web App 的开发,有几种选择:1. 使用诸如 Vite 之类的工具创建项目,2. 使用 Next.js 等框架提供的脚手架创建项目,3. 使用 webpack 从零定制项目。这里选择了第 3 个方案,理由如下:
- 需要对编译打包的输出进行定制,例如去掉文件
hash,以防止每次文件生成之后都要更新CMakeLists.txt文件。 - 项目足够简单,不需要使用第三方框架。
由于不需要 hash ,webpack.config.js 的配置可以跟下边类似。
1module.exports = {
2 ...
3 output: {
4 filename: 'app.js',
5 publicPath: './',
6 },
7 resolve: {
8 extensions: ['.ts', '.tsx', '.js', '.jsx'],
9 },
10 plugins: [
11 new MiniCssExtractPlugin({
12 filename: 'style.css',
13 }),
14 ],
15 ...
16};
值得注意的是,需要在 html 文件中插入 Qt 提供的 qwebchannel.js 文件,这个文件是 Qt 自动嵌入到资源系统里的。
1<script src="qrc:/qtwebchannel/qwebchannel.js"></script>
去掉了 文件 hash, Code Splitting 之后,打包输出的文件只有 index.html, app.js 和 style.css 。接下来就可以打包进资源了。
1qt_add_resources(App "html"
2 PREFIX "/"
3 FILES
4 html/index.html
5 html/app.js
6 html/style.css
7)
资源加载与渲染
接下来的流程与文章 WebEngine Markdown Editor Example 类似。鉴于 APP 既需要支持历史消息,又需要支持用户输入和 AI 返回的消息。那么需要向 Qt WebChannel 注册两个对象
1channel->registerObject(QStringLiteral("histories"), &m_histories);
2channel->registerObject(QStringLiteral("message"), &m_message);
对于历史消息、用户输入的消息直接进行渲染,对于 AI 返回的消息,则添加打字效果。
1useEffect(() => {
2 new QWebChannel(qt.webChannelTransport, function (channel) {
3 const histories = channel.objects.histories;
4 const message = channel.objects.message;
5
6 if (histories.text) {
7 setMessages(JSON.parse(histories.text));
8 }
9
10 history.textChanged.connect((histories: string) => {
11 if (histories.length > 0) {
12 setMessages(JSON.parse(histories));
13 }
14 });
15
16 message.textChanged.connect((message: string) => {
17 if (message.length > 0) {
18 setMessages((prev) => [
19 ...prev,
20 {
21 ...JSON.parse(message),
22 typing: true,
23 },
24 ]);
25 }
26 });
27 });
28}, [setMessages]);



效果如上图所示。
总结
Qt WebChannel 提供了 JS Bridge 类似的功能,使得 JavaScript 代码能够与 Native 代码进行交互。方便编写跨平台的 Hybrid App。