i18n(zh-cn): translate /develop/calling-rust.mdx (#3324)

Co-authored-by: Paul Valladares <85648028+dreyfus92@users.noreply.github.com>
This commit is contained in:
KikkiZ
2025-06-27 00:35:03 +08:00
committed by GitHub
parent 995c59ed1a
commit d60cfe9951
4 changed files with 1114 additions and 3 deletions

View File

@@ -36,7 +36,7 @@ Command names must be unique.
Commands defined in the `lib.rs` file cannot be marked as `pub` due to a limitation in the glue code generation.
You will see an error like this if you mark it as a public function:
````
```
error[E0255]: the name `__cmd__command_name` is defined multiple times
--> src/lib.rs:28:8
|
@@ -47,6 +47,7 @@ error[E0255]: the name `__cmd__command_name` is defined multiple times
|
= note: `__cmd__command_name` must be defined only once in the macro namespace of this module
```
:::
You will have to provide a list of your commands to the builder function like so:
@@ -59,7 +60,7 @@ pub fn run() {
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
````
```
Now, you can invoke the command from your JavaScript code:
@@ -236,7 +237,7 @@ invoke('login', { user: 'tauri', password: '0j4rijw8=' })
As mentioned above, everything returned from commands must implement [`serde::Serialize`], including errors.
This can be problematic if you're working with error types from Rust's std library or external crates as most error types do not implement it.
In simple scenarios you can use `map_err` to convert these errors to `String`s:
In simple scenarios you can use `map_err` to convert these errors to `String`:
```rust
#[tauri::command]

View File

@@ -0,0 +1,141 @@
---
title: 从 Rust 调用前端
---
`@tauri-apps/api` NPM 包提供 API 来监听全局事件和特定于 webview 的事件。
- 监听全局事件
```ts
import { listen } from '@tauri-apps/api/event';
type DownloadStarted = {
url: string;
downloadId: number;
contentLength: number;
};
listen<DownloadStarted>('download-started', (event) => {
console.log(
`downloading ${event.payload.contentLength} bytes from ${event.payload.url}`
);
});
```
- 监听特定的 webview 事件
```ts
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWebview = getCurrentWebviewWindow();
appWebview.listen<string>('logged-in', (event) => {
localStorage.setItem('session-token', event.payload);
});
```
`listen` 函数会在应用程序的整个生命周期内保持事件监听器的注册。
要停止监听某个事件,可以使用 `listen` 函数返回的 `unlisten` 函数:
```js
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('download-started', (event) => {});
unlisten();
```
:::note
当执行上下文超出范围时(例如卸载组件时),请始终使用 unlisten 函数。
当页面重新加载或导航至其他 URL 时监听器将自动注销。但这不适用于单页应用SPA路由器。
:::
此外, Tauri 还提供了一个实用函数,用于精确监听一次事件:
```js
import { once } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
once('ready', (event) => {});
const appWebview = getCurrentWebviewWindow();
appWebview.once('ready', () => {});
```
:::note
前端发出的事件也会触发这些 API 注册的监听器。更多信息请参阅 [从前端调用 Rust] 文档。
:::
#### 监听 Rust 上的事件
全局事件和特定于 webview 的事件也会传递给在 Rust 中注册的监听器。
- 监听全局事件
```rust title="src-tauri/src/lib.rs"
use tauri::Listener;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
app.listen("download-started", |event| {
if let Ok(payload) = serde_json::from_str::<DownloadStarted>(&event.payload()) {
println!("downloading {}", payload.url);
}
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
- 监听特定的 webview 事件
```rust title="src-tauri/src/lib.rs"
use tauri::{Listener, Manager};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let webview = app.get_webview_window("main").unwrap();
webview.listen("logged-in", |event| {
let session_token = event.data;
// save token..
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
`listen` 函数会在应用程序的整个生命周期内保持事件监听器的注册。
要停止监听某个事件,可以使用 `unlisten` 函数:
```rust
// 在事件处理程序作用域外取消监听:
let event_id = app.listen("download-started", |event| {});
app.unlisten(event_id);
// 当满足某些事件条件时取消监听:
let handle = app.handle().clone();
app.listen("status-changed", |event| {
if event.data == "ready" {
handle.unlisten(event.id);
}
});
```
此外, Tauri 还提供了一个实用函数,用于精确监听一次事件:
```rust
app.once("ready", |event| {
println!("app is ready");
});
```
在这种情况下,事件监听器在第一次触发后立即取消注册。
[从前端调用 Rust]: /develop/calling-rust/

View File

@@ -0,0 +1,270 @@
---
title: 从 Rust 调用前端
i18nReady: true
---
import { Content as FrontendListen } from './_sections/frontend-listen.mdx';
本文档包含如何从 Rust 代码与应用程序前端通信的指南。
要了解如何从前端与 Rust 代码通信,请参阅 [从前端调用 Rust] 。
Tauri 应用程序的 Rust 端可以利用 Tauri 事件系统、
使用通道或直接评估 JavaScript 代码来调用前端。
## 事件系统
Tauri 提供了一个简单的事件系统,您可以使用它在 Rust 和前端之间进行双向通信。
事件系统是为需要传输少量数据或需要实现多消费者多生产者模式(例如推送通知系统)的情况而设计的。
事件系统并非为低延迟或高吞吐量场景而设计。
请参阅 [通道部分](#通道) ,了解针对流数据优化的实现。
Tauri 命令和 Tauri 事件之间的主要区别在于,事件没有强类型支持,
事件有效负载始终是 JSON 字符串,这使得它们不适合更大的消息,
并且不支持 [功能] 系统对事件数据和渠道进行细粒度控制。
[AppHandle] 和 [WebviewWindow] 类型实现了事件系统特征 [Listener] 和 [Emitter] 。
事件要么是全局的(传递给所有监听器),要么是特定于 webview 的(仅传递给与给定标签匹配的 webview
### 全局事件
要触发全局事件,您可以使用 [Emitter#emit] 函数:
```rust title="src-tauri/src/lib.rs"
use tauri::{AppHandle, Emitter};
#[tauri::command]
fn download(app: AppHandle, url: String) {
app.emit("download-started", &url).unwrap();
for progress in [1, 15, 50, 80, 100] {
app.emit("download-progress", progress).unwrap();
}
app.emit("download-finished", &url).unwrap();
}
```
:::note
全局事件传递给**所有**监听者
:::
### Webview 事件
要向特定 webview 注册的监听器触发事件​​,您可以使用 [Emitter#emit_to] 函数:
```rust title="src-tauri/src/lib.rs"
use tauri::{AppHandle, Emitter};
#[tauri::command]
fn login(app: AppHandle, user: String, password: String) {
let authenticated = user == "tauri-apps" && password == "tauri";
let result = if authenticated { "loggedIn" } else { "invalidCredentials" };
app.emit_to("login", "login-result", result).unwrap();
}
```
也可以通过调用 [Emitter#emit_filter] 来触发 webview 列表的事件。
以下示例中,我们向主 webview 和文件查看器 webview 发出一个打开文件事件:
```rust title="src-tauri/src/lib.rs"
use tauri::{AppHandle, Emitter, EventTarget};
#[tauri::command]
fn open_file(app: AppHandle, path: std::path::PathBuf) {
app.emit_filter("open-file", path, |target| match target {
EventTarget::WebviewWindow { label } => label == "main" || label == "file-viewer",
_ => false,
}).unwrap();
}
```
:::note
Webview 特有的事件**不会**被触发到常规的全局事件监听器中。
要监听**任何**事件,您必须使用 `listen_any` 函数而不是 `listen` 函数,
后者将监听器定义为所有已发出事件的集合。
:::
### 事件负载
事件负载可以是任何 [可序列化][Serialize] 的类型,只要它实现了 [Clone] 接口。
让我们来进一步改进下载事件的示例,使用一个对象在每个事件中发出更多信息:
```rust title="src-tauri/src/lib.rs"
use tauri::{AppHandle, Emitter};
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct DownloadStarted<'a> {
url: &'a str,
download_id: usize,
content_length: usize,
}
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct DownloadProgress {
download_id: usize,
chunk_length: usize,
}
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct DownloadFinished {
download_id: usize,
}
#[tauri::command]
fn download(app: AppHandle, url: String) {
let content_length = 1000;
let download_id = 1;
app.emit("download-started", DownloadStarted {
url: &url,
download_id,
content_length
}).unwrap();
for chunk_length in [15, 150, 35, 500, 300] {
app.emit("download-progress", DownloadProgress {
download_id,
chunk_length,
}).unwrap();
}
app.emit("download-finished", DownloadFinished { download_id }).unwrap();
}
```
### 监听事件
Tauri 提供 API 来监听 webview 和 Rust 中的事件。
#### 监听前端事件
<FrontendListen />
## 通道
事件系统旨在提供简单的双向通信,并可在应用程序中全局使用。
其底层直接执行 JavaScript 代码,因此可能不适合发送大量数据。
通道旨在快速传递有序数据。它们在内部用于流式传输操作,
例如下载进度、子进程输出和 WebSocket 消息。
让我们重写下载命令示例以使用通道而不是事件系统:
```rust title="src-tauri/src/lib.rs"
use tauri::{AppHandle, ipc::Channel};
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", rename_all_fields = "camelCase", tag = "event", content = "data")]
enum DownloadEvent<'a> {
Started {
url: &'a str,
download_id: usize,
content_length: usize,
},
Progress {
download_id: usize,
chunk_length: usize,
},
Finished {
download_id: usize,
},
}
#[tauri::command]
fn download(app: AppHandle, url: String, on_event: Channel<DownloadEvent>) {
let content_length = 1000;
let download_id = 1;
on_event.send(DownloadEvent::Started {
url: &url,
download_id,
content_length,
}).unwrap();
for chunk_length in [15, 150, 35, 500, 300] {
on_event.send(DownloadEvent::Progress {
download_id,
chunk_length,
}).unwrap();
}
on_event.send(DownloadEvent::Finished { download_id }).unwrap();
}
```
调用下载命令时,您必须创建通道并将其作为参数提供:
```ts
import { invoke, Channel } from '@tauri-apps/api/core';
type DownloadEvent =
| {
event: 'started';
data: {
url: string;
downloadId: number;
contentLength: number;
};
}
| {
event: 'progress';
data: {
downloadId: number;
chunkLength: number;
};
}
| {
event: 'finished';
data: {
downloadId: number;
};
};
const onEvent = new Channel<DownloadEvent>();
onEvent.onmessage = (message) => {
console.log(`got download event ${message.event}`);
};
await invoke('download', {
url: 'https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-schema-generator/schemas/config.schema.json',
onEvent,
});
```
## 执行 JavaScript
要在 webview 上下文中直接执行任何 JavaScript 代码,您可以使用 [`WebviewWindow#eval`] 函数:
```rust title="src-tauri/src/lib.rs"
use tauri::Manager;
tauri::Builder::default()
.setup(|app| {
let webview = app.get_webview_window("main").unwrap();
webview.eval("console.log('hello from Rust')")?;
Ok(())
})
```
如果要执行的脚本不是那么简单并且必须使用来自 Rust 对象的输入,
我们建议使用 [serialize-to-javascript] 包。
[`WebviewWindow#eval`]: https://docs.rs/tauri/2.0.0/tauri/webview/struct.WebviewWindow.html#method.eval
[serialize-to-javascript]: https://docs.rs/serialize-to-javascript/latest/serialize_to_javascript/
[AppHandle]: https://docs.rs/tauri/2.0.0/tauri/struct.AppHandle.html
[WebviewWindow]: https://docs.rs/tauri/2.0.0/tauri/webview/struct.WebviewWindow.html
[Listener]: https://docs.rs/tauri/2.0.0/tauri/trait.Listener.html
[Emitter]: https://docs.rs/tauri/2.0.0/tauri/trait.Emitter.html
[Emitter#emit]: https://docs.rs/tauri/2.0.0/tauri/trait.Emitter.html#tymethod.emit
[Emitter#emit_to]: https://docs.rs/tauri/2.0.0/tauri/trait.Emitter.html#tymethod.emit_to
[Emitter#emit_filter]: https://docs.rs/tauri/2.0.0/tauri/trait.Emitter.html#tymethod.emit_filter
[Clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html
[Serialize]: https://serde.rs/impl-serialize.html
[从前端调用 Rust]: /develop/calling-rust/
[功能]: /security/capabilities/

View File

@@ -0,0 +1,699 @@
---
title: 从前端调用 Rust
i18nReady: true
---
import { Content as FrontendListen } from './_sections/frontend-listen.mdx';
本文档包含有关如何从应用程序前端与 Rust 代码通信的指南。
要了解如何从 Rust 代码与前端通信,请参阅 [从 Rust 调用前端] 。
Tauri 提供了一个 [命令](#命令) 原语,用于以类型安全的方式访问 Rust 函数,以及一个更加动态的 [事件系统](#事件系统) 。
## 命令
Tauri 提供了一个简单但功能强大的 `command` 系统,用于从 Web 应用调用 Rust 函数。
命令可以接受参数并返回结果。它们还可以返回错误或是 `async` 执行。
### 基础示例
您可以在 `src-tauri/src/lib.rs` 文件中定义命令。
要创建命令,只需添加一个函数并用 `#[tauri::command]` 注释它:
```rust title="src-tauri/src/lib.rs"
#[tauri::command]
fn my_custom_command() {
println!("I was invoked from JavaScript!");
}
```
:::note
命令名称必须是唯一的。
:::
:::note
由于胶水代码生成的限制, `lib.rs` 文件中定义的命令无法标记为 `pub` 。
如果将其标记为公共函数,您将看到如下错误:
```
error[E0255]: the name `__cmd__command_name` is defined multiple times
--> src/lib.rs:28:8
|
27 | #[tauri::command]
| ----------------- previous definition of the macro `__cmd__command_name` here
28 | pub fn x() {}
| ^ `__cmd__command_name` reimported here
|
= note: `__cmd__command_name` must be defined only once in the macro namespace of this module
```
:::
您必须向构建器函数提供命令列表,如下所示:
```rust title="src-tauri/src/lib.rs" ins={4}
#[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 代码调用该命令:
```javascript
// 使用 Tauri API npm 包时:
import { invoke } from '@tauri-apps/api/core';
// 使用 Tauri 全局脚本(不使用 npm 包时)
// 确保在 `tauri.conf.json` 中设置 `app.withGlobalTauri` 为 true
const invoke = window.__TAURI__.core.invoke;
// 调用命令
invoke('my_custom_command');
```
#### 在独立的模块中定义命令
如果您的应用程序定义了很多组件或者对它们进行了分组,
那么您可以在单独的模块中定义命令,而不是使 `lib.rs` 文件变得臃肿。
作为示例,我们在 `src-tauri/src/commands.rs` 文件中定义一个命令:
```rust title="src-tauri/src/commands.rs"
#[tauri::command]
pub fn my_custom_command() {
println!("I was invoked from JavaScript!");
}
```
:::note
在单独的模块中定义命令时,应将其标记为 `pub` 。
:::
:::note
命令名称不局限于模块,因此即使在模块之间它们也必须是唯一的。
:::
在 `lib.rs` 文件中,定义模块并相应地提供命令列表;
```rust title="src-tauri/src/lib.rs" ins={6}
mod commands;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![commands::my_custom_command])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
注意命令列表中的 `commands::` 前缀,它表示命令函数的完整路径。
此示例中的命令名称是 `my_custom_command` ,因此您仍然可以通过执行
`invoke("my_custom_command")` 来调用它。在您的前端, `commands::` 前缀将被忽略。
#### WASM
当前端使用不带参数的 `invoke()` 调用 `Rust` 时,您需要按如下方式调整前端代码。
原因是 Rust 不支持可选参数。
```rust ins={4-5}
#[wasm_bindgen]
extern "C" {
// 没有参数的 invoke
#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = invoke)]
async fn invoke_without_args(cmd: &str) -> JsValue;
// (默认)含参的 invoke
#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])]
async fn invoke(cmd: &str, args: JsValue) -> JsValue;
// 他们需要拥有不同的名字!
}
```
### 传递参数
您的命令处理程序可以接受参数:
```rust
#[tauri::command]
fn my_custom_command(invoke_message: String) {
println!("I was invoked from JavaScript, with this message: {}", invoke_message);
}
```
参数应以小驼峰命名,并作为 JSON 对象的键的传递:
```javascript
invoke('my_custom_command', { invokeMessage: 'Hello!' });
```
:::note
您可以将 `snake_case` 用于具有 `rename_all` 属性的参数:
```rust
#[tauri::command(rename_all = "snake_case")]
fn my_custom_command(invoke_message: String) {}
```
```javascript
invoke('my_custom_command', { invoke_message: 'Hello!' });
```
:::
参数可以是任何类型,只要它们实现 [`serde::Deserialize`] 。
相应的 JavaScript 代码:
```javascript
invoke('my_custom_command', { invoke_message: 'Hello!' });
```
### 返回数据
命令处理程序也可以返回数据:
```rust
#[tauri::command]
fn my_custom_command() -> String {
"Hello from Rust!".into()
}
```
`invoke` 函数返回一个使用返回值解析的 `promise`
```javascript
invoke('my_custom_command').then((message) => console.log(message));
```
返回的数据可以是任何类型,只要它实现 [`serde::Serialize`] 。
#### 返回数组缓冲区
实现了 [`serde::Serialize`] 的返回值在将结果发送到前端时被序列化为 JSON。
如果您尝试返回大量数据(例如文件或下载 HTTP response ),这可能会减慢应用程序的速度。
要以优化的方式返回数组缓冲区,请使用 [`tauri::ipc::Response`]
```rust
use tauri::ipc::Response;
#[tauri::command]
fn read_file() -> Response {
let data = std::fs::read("/path/to/file").unwrap();
tauri::ipc::Response::new(data)
}
```
### 错误处理
如果您的处理程序可能失败并且需要能够返回错误,请让函数返回 `Result`
```rust
#[tauri::command]
fn login(user: String, password: String) -> Result<String, String> {
if user == "tauri" && password == "tauri" {
// resolve
Ok("logged_in".to_string())
} else {
// reject
Err("invalid credentials".to_string())
}
}
```
如果命令返回错误,则 promise 将抛出错误,否则,它将正常运行:
```javascript
invoke('login', { user: 'tauri', password: '0j4rijw8=' })
.then((message) => console.log(message))
.catch((error) => console.error(error));
```
如上所述,命令返回的所有内容都必须实现 [`serde::Serialize`] ,包括错误。
如果您使用 Rust 的 std 库或外部包中的错误类型,这可能会有问题,因为大多数错误类型都没有实现它。
在简单的场景中,您可以使用 `map_err` 将这些错误转换为 `String`
```rust
#[tauri::command]
fn my_custom_command() -> Result<(), String> {
std::fs::File::open("path/to/file").map_err(|err| err.to_string())?;
// 成功时返回 `null`
Ok(())
}
```
由于这不太符合惯用习惯,您可能需要创建自己的错误类型,该类型实现了 `serde::Serialize` 。
在下面的示例中,我们使用 [`thiserror`] crate 来帮助创建错误类型。
它允许您通过派生 `thiserror::Error` 特质将枚举转换为错误类型。
您可以查阅其文档以了解更多详细信息。
```rust
// 创建在我们程序中可能发生的所有错误
#[derive(Debug, thiserror::Error)]
enum Error {
#[error(transparent)]
Io(#[from] std::io::Error)
}
// 我们需要手动实现 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> {
// 这里将会返回一个错误
std::fs::File::open("path/that/does/not/exist")?;
// 成功时返回 `null`
Ok(())
}
```
自定义错误类型的优点是可以明确显示所有可能的错误,以便读者能够快速识别可能发生的错误。
这可以为其他人(以及您自己)在以后审查和重构代码时节省大量时间。
它还可以让你完全控制错误类型的序列化方式。在上面的例子中,
我们只是将错误消息作为字符串返回,但你可以为每个错误分配一个代码,
这样就可以更轻松地将其映射到类似的 TypeScript 错误枚举,例如:
```rust
#[derive(Debug, thiserror::Error)]
enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("failed to parse as string: {0}")]
Utf8(#[from] std::str::Utf8Error),
}
#[derive(serde::Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
enum ErrorKind {
Io(String),
Utf8(String),
}
impl serde::Serialize for Error {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let error_message = self.to_string();
let error_kind = match self {
Self::Io(_) => ErrorKind::Io(error_message),
Self::Utf8(_) => ErrorKind::Utf8(error_message),
};
error_kind.serialize(serializer)
}
}
#[tauri::command]
fn read() -> Result<Vec<u8>, Error> {
let data = std::fs::read("/path/to/file")?;
Ok(data)
}
```
在您的前端您现在会得到一个 `{ kind: 'io' | 'utf8', message: string }` 错误对象:
```ts
type ErrorKind = {
kind: 'io' | 'utf8';
message: string;
};
invoke('read').catch((e: ErrorKind) => {});
```
### 异步命令
Tauri 优先使用异步命令来执行繁重的工作,而不会导致 UI 冻结或减速。
:::note
异步命令使用 [`async_runtime::spawn`] 在单独的异步任务上执行。
除非使用 _#[tauri::command(async)]_ 定义,否则没有 _async_ 关键字的命令将在主线程上执行。
:::
**如果您的命令需要异步运行,只需将其声明为 `async` 。**
:::caution
使用 Tauri 创建异步函数时需要小心谨慎。
目前,您不能简单地在异步函数的签名中包含借用的参数。
此类类型的一些常见示例是 `&str` 和 `State<'_, Data>` 。
此限制在此处跟踪: https://github.com/tauri-apps/tauri/issues/2533 ,解决方法如下所示。
:::
使用借用类型时,您必须进行额外的更改。以下是您的两个主要选项:
**选项 1**:将类型转换为非借用类型,例如 `&str` 转为 `String` 。这可能不适用于所有类型,例如 `State<'_, Data>` 。
_例子_
```rust
// 在声明异步函数时使用 String 而不是 &str因为 &str 是借用的,因此不支持
#[tauri::command]
async fn my_custom_command(value: String) -> String {
// 调用另一个异步函数并等待它完成
some_async_function().await;
value
}
```
**选项 2**:将返回类型包装在 [`Result`] 中。这个实现起来有点困难,但适用于所有类型。
使用返回类型 `Result<a, b>` ,将 `a` 替换为您希望返回的类型,或者 `()` 如果您希望返回 `null`
并将 `b` 替换为错误类型以在出现问题时返回,或者 `()` 如果您希望不返回可选错误。例如:
- `Result<String, ()>` 返回一个 String ,且不会返回错误。
- `Result<(), ()>` 返回 `null`。
- `Result<bool, Error>` 返回一个 bool 值或一个错误,如上面的 [错误处理](#错误处理) 部分所示。
_例子_
```rust
// 返回一个 Result<String, ()> 以绕过借用问题
#[tauri::command]
async fn my_custom_command(value: &str) -> Result<String, ()> {
// 调用另一个异步函数并等待它完成
some_async_function().await;
// 注意:返回值必须用 Ok() 包装。
Ok(format!(value))
}
```
##### 从 JavaScript 调用
由于从 JavaScript 调用命令已经返回一个 promise ,因此它的工作方式与任何其他命令一样:
```javascript
invoke('my_custom_command', { value: 'Hello, Async!' }).then(() =>
console.log('Completed!')
);
```
### 通道
Tauri 通道是推荐的流式数据传输机制,例如将 HTTP 响应流式传输到前端。
以下示例读取一个文件,并以 4096 字节为单位的块通知前端传输进度:
```rust
use tokio::io::AsyncReadExt;
#[tauri::command]
async fn load_image(path: std::path::PathBuf, reader: tauri::ipc::Channel<&[u8]>) {
// 为了简单起见,本示例未包含错误处理
let mut file = tokio::fs::File::open(path).await.unwrap();
let mut chunk = vec![0; 4096];
loop {
let len = file.read(&mut chunk).await.unwrap();
if len == 0 {
// 读到文件末尾时结束循环
break;
}
reader.send(&chunk).unwrap();
}
}
```
请参阅 [通道文档] 以了解更多信息。
### 在命令中访问 WebviewWindow
命令可以访问调用该消息的 `WebviewWindow` 实例:
```rust title="src-tauri/src/lib.rs"
#[tauri::command]
async fn my_custom_command(webview_window: tauri::WebviewWindow) {
println!("WebviewWindow: {}", webview_window.label());
}
```
### 在命令中访问 AppHandle
命令可以访问 `AppHandle` 实例:
```rust title="src-tauri/src/lib.rs"
#[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` 在命令中访问状态:
```rust title="src-tauri/src/lib.rs"
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 命令还可以访问完整的 `tauri::ipc::Request` 对象,其中包括原始主体有效负载和请求标头。
```rust
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("unexpected request body")]
RequestBodyMustBeRaw,
#[error("missing `{0}` header")]
MissingHeader(&'static str),
}
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 upload(request: tauri::ipc::Request) -> Result<(), Error> {
let tauri::ipc::InvokeBody::Raw(upload_data) = request.body() else {
return Err(Error::RequestBodyMustBeRaw);
};
let Some(authorization_header) = request.headers().get("Authorization") else {
return Err(Error::MissingHeader("Authorization"));
};
// upload...
Ok(())
}
```
在前端,您可以调用 `invoke()` 通过在 payload 参数上提供 ArrayBuffer
或 Uint8Array 来发送原始请求主体,并在第三个参数中包含请求标头:
```js
const data = new Uint8Array([1, 2, 3]);
await __TAURI__.core.invoke('upload', data, {
headers: {
Authorization: 'apikey',
},
});
```
### 创建多个命令
`tauri::generate_handler!` 宏接受一个命令数组。要注册多个命令,你不能多次调用 invoke_handler。
只有最后一个将使用 call 。您必须将每个命令传递给` tauri::generate_handler!` 。
```rust title="src-tauri/src/lib.rs"
#[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");
}
```
### 完整示例
上述特性中的任何或所有功能都可以组合使用:
```rust title="src-tauri/src/lib.rs"
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");
}
```
```javascript
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));
```
## 事件系统
事件系统是前端和 Rust 之间更简单的通信机制。
与命令不同,事件不是类型安全的,始终是异步的,无法返回值,并且仅支持 JSON 格式的负载。
### 全局事件
要触发全局事件,您可以使用 [event.emit] 或 [WebviewWindow#emit] 函数:
```js
import { emit } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
// emit(eventName, payload)
emit('file-selected', '/path/to/file');
const appWebview = getCurrentWebviewWindow();
appWebview.emit('route-changed', { url: window.location.href });
```
:::note
全局事件将传递给**所有**监听者
:::
### Webview 事件
要向特定 webview 注册的监听器触发事件​​,您可以使用 [event.emitTo] 或 [WebviewWindow#emitTo] 函数:
```js
import { emitTo } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
// emitTo(webviewLabel, eventName, payload)
emitTo('settings', 'settings-update-requested', {
key: 'notification',
value: 'all',
});
const appWebview = getCurrentWebviewWindow();
appWebview.emitTo('editor', 'file-changed', {
path: '/path/to/file',
contents: 'file contents',
});
```
:::note
Webview 特有的事件**不会**被触发到常规的全局事件监听器中。
要监听**所有**事件,您必须为 [event.listen] 函数提供 `{ target: { kind: 'Any' } }`
选项,该选项将监听器定义为所有已发出事件的集合:
```js
import { listen } from '@tauri-apps/api/event';
listen(
'state-changed',
(event) => {
console.log('got state changed event', event);
},
{
target: { kind: 'Any' },
}
);
```
:::
### 监听事件
<FrontendListen />. 要了解如何从 Rust 代码中监听事件和发出事件,请参阅 [Rust
事件系统文档] 。
[从 Rust 调用前端]: /develop/calling-frontend/
[`async_runtime::spawn`]: https://docs.rs/tauri/2.0.0/tauri/async_runtime/fn.spawn.html
[`serde::serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
[`serde::deserialize`]: https://docs.serde.rs/serde/trait.Deserialize.html
[`tauri::ipc::Response`]: https://docs.rs/tauri/2.0.0/tauri/ipc/struct.Response.html
[`tauri::ipc::Request`]: https://docs.rs/tauri/2.0.0/tauri/ipc/struct.Request.html
[`thiserror`]: https://github.com/dtolnay/thiserror
[`result`]: https://doc.rust-lang.org/std/result/index.html
[event.emit]: /reference/javascript/api/namespaceevent/#emit
[event.listen]: /reference/javascript/api/namespaceevent/#listen
[WebviewWindow#emit]: /reference/javascript/api/namespacewebviewwindow/#emit
[event.emitTo]: /reference/javascript/api/namespaceevent/#emitto
[WebviewWindow#emitTo]: /reference/javascript/api/namespacewebviewwindow/#emitto
[Rust 事件系统文档]: /develop/calling-frontend/#event-system
[通道文档]: /develop/calling-frontend/#channels
[Calling Rust from the Frontend]: /develop/calling-rust/