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 个方案,理由如下:

  1. 需要对编译打包的输出进行定制,例如去掉文件 hash,以防止每次文件生成之后都要更新 CMakeLists.txt 文件。
  2. 项目足够简单,不需要使用第三方框架。

由于不需要 hashwebpack.config.js 的配置可以跟下边类似。

module.exports = {
  ...
  output: {
    filename: 'app.js',
    publicPath: './',
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'style.css',
    }),
  ],
  ...
};

值得注意的是,需要在 html 文件中插入 Qt 提供的 qwebchannel.js 文件,这个文件是 Qt 自动嵌入到资源系统里的。

<script src="qrc:/qtwebchannel/qwebchannel.js"></script>

去掉了 文件 hash, Code Splitting 之后,打包输出的文件只有 index.html, app.jsstyle.css 。接下来就可以打包进资源了。

qt_add_resources(App "html"
    PREFIX "/"
    FILES
        html/index.html
        html/app.js
        html/style.css
)

资源加载与渲染

接下来的流程与文章 WebEngine Markdown Editor Example 类似。鉴于 APP 既需要支持历史消息,又需要支持用户输入和 AI 返回的消息。那么需要向 Qt WebChannel 注册两个对象

channel->registerObject(QStringLiteral("histories"), &m_histories);
channel->registerObject(QStringLiteral("message"), &m_message);

对于历史消息、用户输入的消息直接进行渲染,对于 AI 返回的消息,则添加打字效果。

useEffect(() => {
  new QWebChannel(qt.webChannelTransport, function (channel) {
    const histories = channel.objects.histories;
    const message = channel.objects.message;

    if (histories.text) {
      setMessages(JSON.parse(histories.text));
    }

    history.textChanged.connect((histories: string) => {
      if (histories.length > 0) {
        setMessages(JSON.parse(histories));
      }
    });

    message.textChanged.connect((message: string) => {
      if (message.length > 0) {
        setMessages((prev) => [
          ...prev,
          {
            ...JSON.parse(message),
            typing: true,
          },
        ]);
      }
    });
  });
}, [setMessages]);

image.png

image 2.png

效果如上图所示。

总结

Qt WebChannel 提供了 JS Bridge 类似的功能,使得 JavaScript 代码能够与 Native 代码进行交互。方便编写跨平台的 Hybrid App

# ReactQt


评论已关闭