开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
前言
Tauri 采用两种不同的方式实现了前后端的双向通信,当然,这里的双向不是特别准确,详细的介绍可以看看官网的文档 Inter-Process Communication, 本文我们将只讲如何使用。
如果不喜欢这两种你也可以用一些特殊的方法实现通信,比如 rust 起个 webswocket 或者起个 http server 也都可以实现一定的通信能力,具体就看需要了。
前端 -> 后端:Command
看这个标题大概也能感受到一点不同,与其说这是一种通信,倒不如说更像是执行命令。
如果你使用的 @tauri-apps/cli 是最新版本的,即 1.0.5 及以上,那你肯定对它不会陌生,因为首屏的 greet 就是通过这种方式实现的。
至于怎么使用我们也可以直接看源码就能了解个大概:
后端首先定义好一个函数,然后加上 #[tauri::command] 宏定义其为 command 类型,然后前端通过调用 invoke + 函数名 + 参数 即可完成通信,结果将通过 promise 即异步的形式进行返回 。
详细官方文档可见 Calling Rust from the frontend
// component/Greet.vue
<script setup lang="ts">
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/tauri";
const greetMsg = ref("");
const name = ref("");
async function greet() {
greetMsg.value = await invoke("greet", { name: name.value });
}
</script>
<template>
<div class="card">
<input id="greet-input" v-model="name" placeholder="Enter a name..." />
<button type="button" @click="greet()">Greet</button>
</div>
<p>{{ greetMsg }}</p>
</template>
// main.rs
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
后端 <-> 前端:Event
了解或熟悉 QT 的肯定都知道它的 signal->slot 通信方式,十分好用也十分强大,而 Tauri 在这里的实现功能上是类似的,由一方发送 event 事件,另一方 listen 监听并捕获事件,然后进行处理。
依然是 greet 的功能实现,但我们这次不返回值,而是通过 event 将结果发送至前端,具体代码如下。
// component/Greet.vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { invoke } from "@tauri-apps/api/tauri";
import { emit, listen } from '@tauri-apps/api/event'
const greetMsg = ref("");
const name = ref("");
async function greet() {
await invoke("greet", { name: name.value });
}
async function listen_to_greet() {
const unlisten = await listen('greet', (event: any) => {
// event.payload 才是实际的结构体
greetMsg.value = event.payload;
});
}
onMounted(() => {
listen_to_greet();
})
</script>
<template>
<div class="card">
<input id="greet-input" v-model="name" placeholder="Enter a name..." />
<button type="button" @click="greet()">Greet</button>
</div>
<p>{{ greetMsg }}</p>
</template>
// main.rs
use tauri::Manager;
#[tauri::command]
fn greet(name: &str, app_handle: tauri::AppHandle) {
let msg = format!("Hello, {}! You've been greeted from Rust!", name);
app_handle.emit_all("greet", msg);
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
细心的小伙伴肯定注意到了,前端也是有能力 emit 的,那是不是说明后端也能被 greet?让我们来试试。
// component/Greet.vue
// component/Greet.vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { invoke } from "@tauri-apps/api/tauri";
import { emit, listen } from '@tauri-apps/api/event'
const greetMsg = ref("");
const name = ref("");
function greet() {
emit("greet", { name: name.value });
}
async function listen_to_greet() {
const unlisten = await listen('hello', (event: any) => {
// event.payload 才是实际的结构体
greetMsg.value = event.payload;
});
}
onMounted(() => {
listen_to_greet();
})
</script>
<template>
<div class="card">
<input id="greet-input" v-model="name" placeholder="Enter a name..." />
<button type="button" @click="greet()">Greet</button>
</div>
<p>{{ greetMsg }}</p>
</template>
// main.rs
use tauri::Manager;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Greet {
name: String
}
fn main() {
tauri::Builder::default()
.setup(|app| {
let callback_app_handle = app.app_handle().clone();
let _event_id = app.listen_global("greet", move |event| {
let greet_msg: Greet = serde_json::from_str(&event.payload().unwrap()).unwrap();
let msg = format!("Hello, {}! You've been greeted from Rust!", greet_msg.name);
println!("{}", msg);
let _ = &callback_app_handle.emit_all("hello", msg);
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
果然也是可以的,虽然不如 command 方便,但能跑就是好的。
细心的小伙伴肯定又发现了,此时我们的后端程序其实发生了挺多变化的,一个是结构体上多了一行宏定义,另一个是我们实际上获得了由后端主动发送 event 的 “自由”,让我们一个一个来说。
首先是宏定义的含义,其实很简单就是允许结构体序列化和解序列化,或者再换个说法,赋予了结构体成为 JSON 和从 JSON 解出结构体的能力,其第三方库 serde 和 serde_json 已经默认帮我们装好了,相当方便,最后一个 debug 则是允许直接打印整个结构体,就是说我们可以通过这样子的代码直接打印整个结构体。
println!("{:?}", some_struct);
其次是后端主动发送 event 的 “自由” ,这其实是相当重要的一件事,但是在本章我们不会继续探讨,而是将其放在下一章状态管理相关内容中探讨,到时我们才能见到它的完成体。
博客版:
Medium 版:
New Acquaintance With Tauri Chapter 3: Communication Between Webpage and Rust