tauri 使用经验之 —— state
tauri 是一个类似 electron 的跨平台(支持 windows/macOS/linux)桌面程序开发平台。都是用前端技术来做桌面UI,不同的是 tauri 没有像 electron 一样打包一个 chromium,而是用系统自带的 webview, 这样打包体积大大减小;另外 tauri 后端使用 rust 语言替代了 electron 的 node.js。这个选择各有利弊,用 rust 有更好的安全、性能保障,但是陡峭的学习曲线可能让新手望而却步,好消息是,大部分时候可能并不需要写 rust, tauri 提供了很多 api 来访问操作系统的资源(比如:fs, shell, process 等,见这里)。不过取决于需求,比如我就没有那么幸运。
我要用它做了一个桌面工具,需要访问数据库并和一个 .net console 程序通信。所以我不得不写一点 rust 代码。今天我想简单谈下 state 的使用,特别是官网没有直接说的用法。
tauri 提供了 command 以便于从前端调用后端“方法”。而我需要保存一些程序状态,并在不同 command 之间传递。官方文档对 state 讲的很粗略(这里),它仅仅给了如下示例代码:
struct MyState(String);
// 在命令的参数上获取 state
#[tauri::command]
fn my_custom_command(state: tauri::State<MyState>) {
assert_eq!(state.0 == "some state value", true);
}
fn main() {
tauri::Builder::default()
.manage(MyState("some state value".into())) // 在这里注入 state
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
我的需求是,执行一个 A command 时,能存入一个东西(比如 url)到 state 中,然后在后续执行 B command 时能从 state 中取出之前存的 url。我刚开始的想法是,先定义一个结构体,里面可以放这个 url。初始时里面是空的,执行 A command 时修改它,执行 B command 时取出。嗯😐,想法完全没有问题,可是对 rust 不熟的我却不知到怎么写出来。我尝试了用 Option, Arc, Mutex ... 各种我不熟悉的数据结构交织在一起,但也没有消灭编译器给的所有红色警告……🤯
就这么简单需求,怎么就这么难呢?!
后来我去看了下 manage 的 API 文档, 才发现它的用法很灵活。比如,manage 可在不同的位置,不同的时期,多次调用,还有一个 state API 能和 manage 配对使用来取出状态量。文档中就有如下代码:
// ... 省略其他无关代码
tauri::Builder::default()
.setup(|app| {
// 多次使用 manage
app.manage(MyInt(0));
app.manage(MyString("tauri".into()));
// 取出 state
let int = app.state::<MyInt>();
assert_eq!(int.0, 0);
let val: State<MyString> = app.state();
assert_eq!(val.0, "tauri");
// .... 省略其他无关代码
Ok(())
})
mange, state api 都能在 AppHandle 中访问。官网中有如何在 command 中如何访问 AppHandle 的示例:
#[tauri::command]
async fn my_custom_command(app_handle: tauri::AppHandle) {
let app_dir = app_handle.path_resolver().app_dir();
use tauri::GlobalShortcutManager;
app_handle.global_shortcut_manager().register("CTRL + U", move || {});
}
至此我的需求解决了,简化代码如下:
#[tauri::command]
fn a_command(app_handle: tauri::AppHandle, front_end_url: String) {
app_handle.manage(front_end_url);
}
#[tauri::command]
fn b_command(app_handle: tauri::AppHandle) {
let url: State<String> = app_handle.state();
// ...
}
哈哈是不是很简单!不过要注意的是,要取出什么,取决于前面定义的类型,且获取前确保 state 已经存入。
好了,今天就到这里了。谢谢!