【Tauri2】014——简单使用listen和emit
前言
【Tauri2】013——前端Window Event与创建Window-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146981362?spm=1001.2014.3001.5502【Tauri2】012——on_window_event函数-CSDN博客
https://blog.csdn.net/qq_63401240/article/details/146909801?spm=1001.2014.3001.5502
前面简单地介绍了window中的事件,分别在前后端使用事件,但是这些事件都是Tauri中的,实际上也可以自定义事件,试试
发送事件emit并在前端监听事件listen
可以全局监听事件,但是笔者想写写通信函数。因此,把事件写在通信函数中。
要想发送事件,需要这个emit方法,而这个emit在AppHandle中,准确地说应该是在AppHandle实现的trait Emittter中
AppHandle in tauri - Rusthttps://docs.rs/tauri/latest/tauri/struct.AppHandle.html#impl-Emitter%3CR%3E-for-AppHandle%3CR%3E看看函数
fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
let event = EventName::new(event)?;
let payload = EmitPayload::Serialize(&payload);
self.manager().emit(event, payload)
}
第一个参数 &self ,实例的引用,结构体实现trait的方法,一般第一个参数都是包含self,不必细说
第二个参数 event: &str,字符串切片,很明显,事件的名字
第三个参数payload: S,S是泛型,泛型约束是Serialize + Clone,简单地说,序列化和克隆
在函数体,实现也比较简单,初始化名字和负载。
从函数定义,可以发现,payload需要实现Serialize + Clone
实现Serialize + Clone
很简单,看看依赖
[dependencies]
tauri = { version = "2.4.1", features = ["image-png", "unstable", "devtools"] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
这个serde 就是实现的关键。
在实现之前,先对项目进行修改一下
其中mod.rs的代码如下
pub mod event;
pub mod window;
在event.rs中实现。代码如下
use serde::Serialize;
#[derive(Serialize, Clone)]
struct User{
id: i32,
name: String,
age: i32,
}
首先,这个derive是属性,用于触发派生宏(proc_macro_derive)的执行,它可以附加到结构体、枚举等类型上,告诉编译器为这些类型生成特定的代码,
Derive - The Rust Referencehttps://doc.rust-lang.org/reference/attributes/derive.html可以通过cargo expand查看一些宏展开之后的代码
【Tauri2】007——Tauri2和cargo expand-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146632055?spm=1001.2014.3001.5502在src-tauri目录,运行如下命令
cargo expand ::command::event
结果如下
pub mod event {
use serde::Serialize;
struct User {
id: i32,
name: String,
age: i32,
}
#[doc(hidden)]
#[allow(
non_upper_case_globals,
unused_attributes,
unused_qualifications,
clippy::absolute_paths,
)]
const _: () = {
#[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate serde as _serde;
#[automatically_derived]
impl _serde::Serialize for User {
fn serialize<__S>(
&self,
__serializer: __S,
) -> _serde::__private::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
let mut __serde_state = _serde::Serializer::serialize_struct(
__serializer,
"User",
false as usize + 1 + 1 + 1,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"id",
&self.id,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"name",
&self.name,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"age",
&self.age,
)?;
_serde::ser::SerializeStruct::end(__serde_state)
}
}
};
#[automatically_derived]
impl ::core::clone::Clone for User {
#[inline]
fn clone(&self) -> User {
User {
id: ::core::clone::Clone::clone(&self.id),
name: ::core::clone::Clone::clone(&self.name),
age: ::core::clone::Clone::clone(&self.age),
}
}
}
}
这段代码还是很复杂的,笔者就简单说一说。
1、#[allow]。
allow是允许的意思,所以意思很明显
non_upper_case_globals:允许非大写的常量名(如 _serde)。
unused_attributes/unused_qualifications:忽略未使用的属性或限定符。
clippy::absolute_paths:允许使用绝对路径(如 ::core::clone)。
2、#[automatically_derived]
标记此实现是自动生成的,避免编译器警告
3、实现Serialize 这个trait和其中的serialize方法。
调用 serialize_struct开始序列化结构体,使用serialize_field 依次序列化每个字段,最终调用end完成序列化。
4、实现Clone这个trait和其中的clone方法。
总之,对于payload,需要实现这两个trait。
创建通信函数,准备发送事件
在event.rs中,关键代码如下
#[command]
pub fn send_event(app: AppHandle, event_name: &str) {
app.emit(
event_name,
User {
id: 1,
name: "John".to_string(),
age: 30,
},
)
.unwrap()
}
注册通信函数。
前端监听
在App.tsx中,设置按钮并绑定按钮的点击事件
async function listenSignal(){
await invoke("send_event",{
event_name:"hello",
});
}
在Events目录下,监听
export default function useWindowEvent() {
let window = getCurrentWindow();
window.listen("hello", (event) => {
console.log(event.payload);
})
}
运行项目
点击触发信号,然后
报错了。报错如下
Uncaught (in promise) invalid args `eventName` for command `send_event`:
command send_event missing required key eventName
居然需要eventName,还需要转化命名方式camelCase,因此,修改代码
async function listenSignal(){
await invoke("send_event",{
eventName:"hello",
});
}
结果如下
居然还需要改参数名字,笔者还不知道
使用cargo expand 看看
cargo expand > expanded.rs
输出到文件中,太多了,powershell展示不全
关键代码如下
match ::tauri::ipc::CommandArg::from_command(::tauri::ipc::CommandItem {
plugin: ::core::option::Option::None,
name: "send_event",
key: "eventName",
message: &__tauri_message__,
acl: &__tauri_acl__,
还真是eventName。
总之,交互成功
前端发送事件emit并在后端监听事件listen
看看函数签名
fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
where
F: Fn(Event) + Send + 'static
第一个参数event ,接受任何能 转换为Striing的类型。
第一个参数handler,类型是泛型F,F的约束是Fn(Event) + Send + 'static。
简单地说。第一个参数一般传&str,第一个参数是闭包或者函数。
通信函数监听
代码如下
#[command]
pub fn listen_event(app:AppHandle, event_name: &str) {
app.listen(event_name, |event| {
let user = event.payload();
println!("user: {:?}", user);
});
}
但是这个payload返回的是字符串切片&str,笔者希望得到一个User
pub fn payload(&self) -> &str {
&self.data
}
这就需要另一个依赖serde_json。
serde_json - Rusthttps://docs.rs/serde_json/latest/serde_json/代码如下
#[command]
pub fn listen_event(app:AppHandle, event_name: &str) {
app.listen(event_name, |event| {
let user:User =serde_json::from_str(event.payload()).unwrap() ;
println!("user: {:?}", user);
});
}
要想把&str变成User,需要反序列化,就需要实现Deserialize这个trait
因为还需要打印User,还需要实现Debug这个trait
因此,User的修改如下
#[derive(Serialize, Clone, Deserialize, Debug)]
struct User {
id: i32,
name: String,
age: i32,
}
前端发送信号
关键代码
let window = getCurrentWindow();
async function triggerSignal(){
window.emit("world",{
id:1,
name:"tom",
age:18,
})
await invoke("listen_event",{
eventName:"world",
});
}
运行,结果如下
其他方法及总结
实际上,除了emit和listen,还有emitTo(emit_to)、once之类的
event | Taurihttps://v2.tauri.app/reference/javascript/api/namespaceevent/#emitto还有取消监听unlisten之类的方法。以后慢慢使用。
当然,也不一定要在通信函数中,也可以在setup或者其他地方。