跳至主要内容

从前端调用 Rust

Tauri 提供了一个简单但功能强大的command系统,用于从 Web 应用程序调用 Rust 函数。命令可以接受参数并返回值。它们还可以返回错误并成为async

基本示例

命令在src-tauri/src/main.rs文件中定义。要创建命令,只需添加一个函数并用#[tauri::command]对其进行注释

#[tauri::command]
fn my_custom_command() {
println!("I was invoked from JS!");
}

您必须像这样向构建器函数提供命令列表

// Also in main.rs
fn main() {
tauri::Builder::default()
// This is where you pass in your commands
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("failed to run app");
}

现在,您可以从 JS 代码调用该命令

// When using the Tauri API npm package:
import { invoke } from '@tauri-apps/api/tauri'
// When using the Tauri global script (if not using the npm package)
// Be sure to set `build.withGlobalTauri` in `tauri.conf.json` to true
const invoke = window.__TAURI__.invoke

// Invoke the command
invoke('my_custom_command')

传递参数

您的命令处理程序可以接受参数

#[tauri::command]
fn my_custom_command(invoke_message: String) {
println!("I was invoked from JS, 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 JS, 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,它会使用返回的值进行解析

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 将被拒绝,否则,它将被解析

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> {
// This will return an error
std::fs::File::open("path/that/does/not/exist").map_err(|err| err.to_string())?;
// Return nothing on success
Ok(())
}

由于这不是很惯用,你可能希望创建自己的错误类型,该类型实现了 serde::Serialize。在下面的示例中,我们使用 thiserror crate 来帮助创建错误类型。它允许你通过派生 thiserror::Error 特性将枚举转换为错误类型。你可以查阅其文档了解更多详细信息。

// create the error type that represents all errors possible in our program
#[derive(Debug, thiserror::Error)]
enum Error {
#[error(transparent)]
Io(#[from] std::io::Error)
}

// we must manually implement serde::Serialize
impl 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> {
// This will return an error
std::fs::File::open("path/that/does/not/exist")?;
// Return nothing on success
Ok(())
}

自定义错误类型的好处是使所有可能的错误都显式化,以便读者可以快速识别可能发生的错误。这可以为其他人(包括你自己)在以后审查和重构代码时节省大量时间。
它还让你可以完全控制错误类型的序列化方式。在上面的示例中,我们只是将错误消息作为字符串返回,但你可以为每个错误分配一个类似于 C 的代码,这样你就可以更轻松地将其映射到类似的 TypeScript 错误枚举,例如。

异步命令

异步函数在 Tauri 中很有用,它可以执行繁重的工作,而不会导致 UI 冻结或变慢。

注意

异步命令使用 async_runtime::spawn 在单独的线程上执行。没有 async 关键字的命令在主线程上执行,除非使用 #[tauri::command(async)] 定义。

如果你的命令需要异步运行,只需将其声明为 async 即可。

警告

使用 Tauri 创建异步函数时需要小心。目前,你不能简单地将借用参数包含在异步函数的签名中。此类的一些常见示例类型是 &strState<'_, Data>。此限制在此处进行跟踪:https://github.com/tauri-apps/tauri/issues/2533,变通方法如下所示。

使用借用类型时,必须进行其他更改。以下是你的两个主要选项

选项 1:将类型(例如 &str)转换为类似的非借用类型(例如 String)。这可能不适用于所有类型,例如 State<'_, Data>

示例

// Declare the async function using String instead of &str, as &str is borrowed and thus unsupported
#[tauri::command]
async fn my_custom_command(value: String) -> String {
// Call another async function and wait for it to finish
some_async_function().await;
format!(value)
}

选项 2:将返回类型包装在 Result 中。此选项的实现难度稍大,但应该适用于所有类型。

使用返回类型 Result<a, b>,其中 a 替换为要返回的类型,如果不想返回任何内容,则替换为 (),将 b 替换为出错时要返回的错误类型,如果不想返回任何可选错误,则替换为 ()。例如

  • Result<String, ()> 返回一个字符串,没有错误。
  • Result<(), ()> 不返回任何内容。
  • Result<bool, Error> 返回一个布尔值或错误,如上文 错误处理 部分所示。

示例

// Return a Result<String, ()> to bypass the borrowing issue
#[tauri::command]
async fn my_custom_command(value: &str) -> Result<String, ()> {
// Call another async function and wait for it to finish
some_async_function().await;
// Note that the return value must be wrapped in `Ok()` now.
Ok(format!(value))
}

从 JS 调用

由于从 JavaScript 调用命令已经返回了一个 Promise,因此它就像任何其他命令一样工作

invoke('my_custom_command', { value: 'Hello, Async!' }).then(() =>
console.log('Completed!')
)

在命令中访问窗口

命令可以访问调用消息的 Window 实例

#[tauri::command]
async fn my_custom_command(window: tauri::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);
}

fn main() {
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"
}

fn main() {
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())
}
}

fn main() {
tauri::Builder::default()
.manage(Database {})
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// Invocation from JS

invoke('my_custom_command', {
number: 42,
})
.then((res) =>
console.log(`Message: ${res.message}, Other Val: ${res.other_val}`)
)
.catch((e) => console.error(e))