mirror of
https://github.com/tauri-apps/tauri-docs.git
synced 2026-01-31 00:35:16 +01:00
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:
@@ -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]
|
||||
|
||||
141
src/content/docs/zh-cn/develop/_sections/frontend-listen.mdx
Normal file
141
src/content/docs/zh-cn/develop/_sections/frontend-listen.mdx
Normal 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/
|
||||
270
src/content/docs/zh-cn/develop/calling-frontend.mdx
Normal file
270
src/content/docs/zh-cn/develop/calling-frontend.mdx
Normal 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/
|
||||
699
src/content/docs/zh-cn/develop/calling-rust.mdx
Normal file
699
src/content/docs/zh-cn/develop/calling-rust.mdx
Normal 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/
|
||||
Reference in New Issue
Block a user