从前端调用 Rust
Tauri 提供了一个简单而强大的 command
系统,用于从你的 Web 应用程序中调用 Rust 函数。
命令可以接受参数和返回值。它们也可以返回错误并且是 async
的。
基本的例子
命令在 src-tauri/src/lib.rs
文件中定义。要创建命令,只需添加一个函数并用 #[tauri::command]
标注它。
#[tauri::command]fn my_custom_command() { println!("I was invoked from JavaScript!");}
你必须向构建函数提供命令列表,如下所示。
#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![my_custom_command]) .run(tauri::generate_context!()) .expect("error while running tauri application");}
现在,你可以从 JavaScript 代码中调用该命令。
// 当使用 Tauri API 的 npm 包时import { invoke } from '@tauri-apps/api/core';
// 使用 Tauri 全局脚本时(如果不使用 npm 包)// 确保在 `tauri.conf.json` 中将 `app.withGlobalTauri` 设置为 true。const invoke = window.__TAURI__.invoke;
// 使用命令invoke('my_custom_command');
传递参数
你的命令处理程序可以接受以下参数。
#[tauri::command]fn my_custom_command(invoke_message: String) { println!("I was invoked from JavaScript, with this message: {}", invoke_message);}
参数应该作为一个带有 camelCase 键的 JSON 对象传递。
invoke('my_custom_command', { invokeMessage: 'Hello!' });
参数可以是任何类型,只要它们实现了 serde::Deserialize
。
请注意,当在 Rust 中使用 snake_case 声明参数时,在 JavaScript 中参数会被转换为 camelCase。
要在 JavaScript 中使用 snake_case,你必须在 tauri::command
语句中声明它。
#[tauri::command(rename_all = "snake_case")]fn my_custom_command(invoke_message: String) { println!("I was invoked from JavaScript, with this message: {}", invoke_message);}
对应的 JavaScript 代码。
invoke('my_custom_command', { invoke_message: 'Hello!' });
返回数据
命令处理程序也可以返回数据。
#[tauri::command]fn my_custom_command() -> String { "Hello from Rust!".into()}
invoke
函数返回一个 promise,其 resolve 接收一个返回值。
invoke('my_custom_command').then((message) => console.log(message));
返回的数据可以是任何类型,只要它实现了 serde::Serialize
。
错误处理
如果你的处理程序可能失败并且需要能够返回一个错误,让函数返回一个 Result
。
#[tauri::command]fn my_custom_command() -> Result<String, String> { // If something fails Err("This failed!".into()) // If it worked Ok("This worked!".into())}
如果命令返回错误,promise 将 reject,否则 resolve。
invoke('my_custom_command') .then((message) => console.log(message)) .catch((error) => console.error(error));
如上所述,从命令返回的所有内容都必须实现 serde::Serialize
,包括错误。
如果您正在处理来自 Rust 的 std 库或外部 crate 的错误类型,则可能会出现问题,因为大多数错误类型都没有实现它。
一般情况下,你可以使用 map_err
将这些错误转换为 String
。
#[tauri::command]fn my_custom_command() -> Result<(), String> { // 这将返回一个错误 std::fs::File::open("path/that/does/not/exist").map_err(|err| err.to_string())?; // 成功返回空 Ok(())}
因为这不是很符合习惯,你可能想要创建自己的错误类型来实现 serde::Serialize
。在下面的例子中,我们使用 thiserror
创建错误类型。
它允许你通过派生 thiserror::Error
特征将枚举转换为错误类型。你可以查阅它的文档了解更多细节。
// 创建 error 类型,表示程序中可能出现的所有错误#[derive(Debug, thiserror::Error)]enum Error { #[error(transparent)] Io(#[from] std::io::Error)}
// 我们必须手动实现 serde::Serializeimpl serde::Serialize for Error { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::ser::Serializer, { serializer.serialize_str(self.to_string().as_ref()) }}
#[tauri::command]fn my_custom_command() -> Result<(), Error> { // 这将返回一个错误 std::fs::File::open("path/that/does/not/exist")?; // 成功返回空 Ok(())}
自定义错误类型的优点是明列所有可能的错误,以便读者快速识别可能发生的错误。这为别人(和你自己)在以后审查和重构代码时节省了大量的时间。
它还让你可以完全控制错误类型被序列化的方式。在上面的例子中,我们简单地将错误消息作为字符串返回,但是你可以为每个错误分配一个类似于 C 的代码,这样你可以更容易地将它映射到一个看起来类似的 TypeScript 错误枚举。
异步命令
异步函数在 Tauri 中有利于执行繁重的工作,不会导致 UI 冻结或减慢。
如果你的命令需要异步运行,只需将其声明为 async
。
在使用借用的类型时,必须进行额外的更改。这是你的两个主要选择。
选项 1:将类型(如 &str
)转换为不允许借用的类似类型(如 String
)。 这可能不适用于所有类型,例如 State<'_, Data>
。
示例:
// 使用 String 而不是 &str 声明异步函数,因为 &str 是借用的,因此不受支持#[tauri::command]async fn my_custom_command(value: String) -> String { // 调用另一个异步函数并等待它完成 some_async_function().await; value}
选项 2:将返回类型包装在一个 Result
中。这个实现起来有点难,但应该适用于所有类型。
使用返回类型 Result<a, b>
,将 a
替换为你想要返回的类型,或者如果你不想返回任何类型,则使用 ()
,如果发生错误,则将 b
替换为错误类型,或者如果你不想返回任何可选错误,则使用 ()
。例如:
Result<String, ()>
返回一个字符串,并且没有错误。Result<(), ()>
什么都不返回。Result<bool, Error>
返回一个布尔值或一个错误,如上面的错误处理部分所示。
示例:
// 返回一个 Result<String, ()> 来绕过借用问题#[tauri::command]async fn my_custom_command(value: &str) -> Result<String, ()> { // 调用另一个异步函数并等待它完成 some_async_function().await; // 注意,现在必须将返回值包装在 `Ok()` 中。 Ok(format!(value))}
从 JavaScript 调用
因为在 JavaScript 中调用这个命令会返回一个 promise,所以它的工作方式和其他命令一样。
invoke('my_custom_command', { value: 'Hello, Async!' }).then(() => console.log('Completed!'));
在命令中访问 WebviewWindow
命令可以访问调用消息的 WebviewWindow
实例。
#[tauri::command]async fn my_custom_command(webview_window: tauri::WebviewWindow) { println!("WebviewWindow: {}", webview_window.label());}
在命令中访问 Window
命令可以访问调用消息的 Window
实例。
#[tauri::command]async fn my_custom_command(window: tauri::Window) { println!("Window: {}", window.label());}
你也可以从 WebviewWindow
中访问 Window
实例:
#[tauri::command]async fn my_custom_command(webview_window: tauri::WebviewWindow) { let window: tauri::Window = webview_window.as_ref().window(); println!("Window: {}", window.label());}
在命令中访问 AppHandle
命令可以访问 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 可以使用 Tauri::Builder
上的 manage
函数来管理状态。
可以使用 tauri::State
命令访问状态。
struct MyState(String);
#[tauri::command]fn my_custom_command(state: tauri::State<MyState>) { assert_eq!(state.0 == "some state value", true);}
#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { tauri::Builder::default() .manage(MyState("some state value".into())) .invoke_handler(tauri::generate_handler![my_custom_command]) .run(tauri::generate_context!()) .expect("error while running tauri application");}
创建多个命令
tauri::generate_handler!
宏接受一个命令数组作为参数,
为了注册多个命令,你不能多次调用 invoke_handler,
只有最后一次调用才会被使用,你必须将每个命令传递给 tauri::generate_handler!
的单次调用。
#[tauri::command]fn cmd_a() -> String { "Command a"}#[tauri::command]fn cmd_b() -> String { "Command b"}
#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![cmd_a, cmd_b]) .run(tauri::generate_context!()) .expect("error while running tauri application");}
完整的示例
上面的任何一个或所有功能都可以组合起来。
struct Database;
#[derive(serde::Serialize)]struct CustomResponse { message: String, other_val: usize,}
async fn some_other_function() -> Option<String> { Some("response".into())}
#[tauri::command]async fn my_custom_command( window: tauri::Window, number: usize, database: tauri::State<'_, Database>,) -> Result<CustomResponse, String> { println!("Called from {}", window.label()); let result: Option<String> = some_other_function().await; if let Some(message) = result { Ok(CustomResponse { message, other_val: 42 + number, }) } else { Err("No result".into()) }}
#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { tauri::Builder::default() .manage(Database {}) .invoke_handler(tauri::generate_handler![my_custom_command]) .run(tauri::generate_context!()) .expect("error while running tauri application");}
import { invoke } from '@tauri-apps/api/core';
// 从 JavaScript 调用invoke('my_custom_command', { number: 42,}) .then((res) => console.log(`Message: ${res.message}, Other Val: ${res.other_val}`) ) .catch((e) => console.error(e));
© 2024 Tauri中文网