Tauri事件监听与清理(前端React)

最近在学习使用Tauri开发桌面应用,对其中很多细节不是很清楚,官方文档还不够完善,网上的资料也不是很多,在这里做个记录,也分享给可能需要的朋友。

前言

Tauri简介

Tauri对于很多人,包括我而言,还是一个新事物,用最简单的来说就是一个基于Rust、类似Electron的GUI框架。所以可以拿来开发跨平台的桌面应用。目前Rust下还没有特别成熟的GUI解决方案,这是为什么我要使用Tauri。

Tauri的核心(后端)是Rust,而前端可以采用任何去前端框架。我前端选用的是React + Typescript,刚好发现,React与Tauri、Typescript与Rust有一些契合之处。

Tauri事件系统

为了传递数据、操作等,需要用到Tauri的事件机制。根据官方文档的介绍,Tauri事件系统是一个多生产者多消费者通信原语,允许在前端和后端之间传递消息。它类似于Tauri的command系统,但是必须在事件处理程序上对负载类型进行检查,并且采用类似通道(channel)的工作方式,简化了从后端到前端的通信。

Tauri后端(Rust)调用前端(JS)

既然Tauri有前端和后端,那么在前端和后端都需要监听事件。如何监听事件,在技术文档中已经有了比较明确的说明。

可能如果是新手(比如我),对于一个不太常见的场景——从后端调用前端,例如当后端完成数据处理等比较复杂的任务以后,将结果发送或者报告给前端,会有一些困惑。在Calling JS from Rust这个issue中给出了比较完善的回答:

  • App HandleWindow,分别针对全局触发和特定窗口触发;
  • 使用 event.window().app_handle()可以从事件获得AppHandle;
  • 使用window.emit_all() 可以让所有窗口(JS前端)接收到事件。

当然在这个情况下,前端需要对相应的事件进行监听。例如,在React中可以在useEffect()中注册事件监听器。

Tauri前端清理事件监听器

之所以要清理事件监听器,是防止因为某些逻辑重复注册监听器而造成资源的浪费和性能的消耗,后端清理事件监听器比较简单,采用app.unlisten()或者window.unlisten()即可,参见技术文档

但是,关于前端如何清理事件监听器,则有些语焉不详。以React为例,查了一些资料,总结如下。

React Hooks注册与清理

在React Hooks中可以使用useEffect()来注册监听器,通常组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect() 函数需返回一个清除函数,所以在useEffect()的返回值里写清理监听器的函数即可。

单个监听器清理

根据技术文档Emitting events consecutively cause hang in front end的讨论,tauri-apps/api/eventlisten函数返回的是一个Promise<UnlistenFn>,这个Promise会解析成一个用来清除监听器的函数。所以,在React useEffect()中的清理方法为:

1
2
3
4
5
6
7
8
9
useEffect(() => {
const unlisten = listen('new_count', event => {
setCount(event.payload.count);
})

return () => {
unlisten.then((f) => f());
};
}, [/* THIS IS THE DEPENDENCY ARRAY. Leaving it empty executes this useEffect ONLY when the component mounts */]);

批量监听器清理

上述写法是对单个React的清理,如果我们注册的监听器比较多,就希望批量清理,参考tgfukuda/**notex-desktop**可以采用如下的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React, { useEffect } from "react";
import { listen, Event as TauriEvent, UnlistenFn } from "@tauri-apps/api/event";

useEffect(() => {
const unlisten: UnlistenFn[] = [];

listen("routing", (e: TauriEvent<string>) => {
history.push({
pathname: e.payload,
});
})
.then((ulf) => {
unlisten.push(ulf);
})
.catch((err) => handleErr(err.message));

listen("success", (e: TauriEvent<Response>) => {
handleSuc(e.payload.message);
})
.then((ulf) => {
unlisten.push(ulf);
})
.catch((err) => handleErr(err.message));

listen("fail", (e: TauriEvent<Response>) => {
handleErr(e.payload.message);
})
.then((ulf) => {
unlisten.push(ulf);
})
.catch(() => {});

return () => {
for (const ulf of unlisten) ulf();
};
// eslint-disable-next-line
}, []);

小结

这就是关于Tauri(Rust+React)事件系统的一个学习笔记,没什么太多可以总结的😅

参考资料

  1. https://tauri.studio/docs/guides/events/
  2. https://github.com/tauri-apps/tauri/discussions/3018
  3. https://github.com/tauri-apps/tauri/issues/3021
  4. https://stackoverflow.com/questions/55360736/how-do-i-window-removeeventlistener-using-react-useeffect
  5. https://www.pluralsight.com/guides/how-to-cleanup-event-listeners-react
  6. https://codepen.io/publicJorn/pen/eYzwENN

番外:封面

这个封面其实是我在新华社微信公众号的封面上偶然看到的,觉得还不错,就去找了一下,发现是法国画家、印象派创始人之一阿尔弗雷德·西斯莱的作品《The Banks of the Canal du Loing at Saint-Mammès》,现藏于爱尔兰国立美术馆

看了一下西斯莱的作品,我觉得总体来看不是很动人,关于 Saint-Mammès的运河,他有很多作品😆,这幅画给我的感觉还不错。

查了一下资料,西斯莱作为法国印象派的创始人,起初并不受看重,死后才获得好评,其生活应该也并不顺遂。在《现代艺术150年》中并没有西斯莱的作品,倒是在介绍”落选者沙龙“的时候,对西斯莱有一些比较鲜活的描述。有兴趣的朋友可以了解一下:

现代艺术150年——3 印象派:现代生活的画家,1870-1890