从前端调用 Rust
本文档包含有关如何从应用前端与 Rust 代码进行通信的指南。要了解如何从你的 Rust 代码与你的前端通信,请参阅 从 Rust 调用前端。
¥This document includes guides on how to communicate with your Rust code from your application frontend. To see how to communicate with your frontend from your Rust code, see Calling the Frontend from Rust.
Tauri 提供了一个 command 原语,用于以类型安全的方式访问 Rust 函数,以及一个更具动态性的 事件系统。
¥Tauri provides a command primitive for reaching Rust functions with type safety, along with an event system that is more dynamic.
命令
¥Commands
Tauri 提供了一个简单但功能强大的 command
系统,用于从你的 Web 应用调用 Rust 函数。命令可以接受参数并返回值。它们还可以返回错误并成为 async
。
¥Tauri provides a simple yet powerful command
system for calling Rust functions from your web app.
Commands can accept arguments and return values. They can also return errors and be async
.
基本示例
¥Basic Example
命令可以在你的 src-tauri/src/lib.rs
文件中定义。要创建命令,只需添加一个函数并用 #[tauri::command]
注释它:
¥Commands can be defined in your src-tauri/src/lib.rs
file.
To create a command, just add a function and annotate it with #[tauri::command]
:
#[tauri::command]fn my_custom_command() { println!("I was invoked from JavaScript!");}
:::note 注意
命令名称必须是唯一的。
¥Command names must be unique.
:::
:::note 注意
由于粘合代码生成的限制,lib.rs
文件中定义的命令无法标记为 pub
。如果你将其标记为公共函数,你将看到类似这样的错误:
¥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 |27 | #[tauri::command] | ----------------- previous definition of the macro `__cmd__command_name` here28 | pub fn x() {} | ^ `__cmd__command_name` reimported here | = 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:
```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 代码中调用命令:
¥Now, you can invoke the command from your JavaScript code:
// When using the Tauri API npm package:import { invoke } from '@tauri-apps/api/core';
// When using the Tauri global script (if not using the npm package)// Be sure to set `app.withGlobalTauri` in `tauri.conf.json` to trueconst invoke = window.__TAURI__.core.invoke;
// Invoke the commandinvoke('my_custom_command');
在单独的模块中定义命令
¥Defining Commands in a Separate Module
如果你的应用定义了很多组件或者它们可以分组,你可以在单独的模块中定义命令,而不是使 lib.rs
文件膨胀。
¥If your application defines a lot of components or if they can be grouped,
you can define commands in a separate module instead of bloating the lib.rs
file.
作为示例,让我们在 src-tauri/src/commands.rs
文件中定义一个命令:
¥As an example let’s define a command in the src-tauri/src/commands.rs
file:
#[tauri::command]pub fn my_custom_command() { println!("I was invoked from JavaScript!");}
:::note 注意
在单独的模块中定义命令时,应将它们标记为 pub
。
¥When defining commands in a separate module they should be marked as pub
.
:::
:::note 注意
命令名称不局限于模块,因此即使在模块之间它们也必须是唯一的。
¥The command name is not scoped to the module so they must be unique even between modules.
:::
在 lib.rs
文件中,定义模块并相应地提供命令列表;
¥In the lib.rs
file, define the module and provide the list of your commands accordingly;
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::
前缀,它表示命令函数的完整路径。
¥Note the commands::
prefix in the command list, which denotes the full path to the command function.
此示例中的命令名称是 my_custom_command
,因此你仍然可以通过在前端执行 invoke("my_custom_command")
来调用它,commands::
前缀将被忽略。
¥The command name in this example is my_custom_command
so you can still call it by executing invoke("my_custom_command")
in your frontend, the commands::
prefix is ignored.
WASM
当使用 Rust 前端调用不带参数的 invoke()
时,你需要调整前端代码,如下所示。原因是 Rust 不支持可选参数。
¥When using a Rust frontend to call invoke()
without arguments, you will need to adapt your frontend code as below.
The reason is that Rust doesn’t support optional arguments.
#[wasm_bindgen]extern "C" { // invoke without arguments #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = invoke)] async fn invoke_without_args(cmd: &str) -> JsValue;
// invoke with arguments (default) #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])] async fn invoke(cmd: &str, args: JsValue) -> JsValue;
// They need to have different names!}
传递参数
¥Passing Arguments
你的命令处理程序可以接受参数:
¥Your command handlers can take arguments:
#[tauri::command]fn my_custom_command(invoke_message: String) { println!("I was invoked from JavaScript, with this message: {}", invoke_message);}
参数应作为带有 camelCase 键的 JSON 对象传递:
¥Arguments should be passed as a JSON object with camelCase keys:
invoke('my_custom_command', { invokeMessage: 'Hello!' });
:::note 注意
你可以将 snake_case
用于具有 rename_all
属性的参数:
¥You can use snake_case
for the arguments with the rename_all
attribute:
#[tauri::command(rename_all = "snake_case")]fn my_custom_command(invoke_message: String) {}
invoke('my_custom_command', { invoke_message: 'Hello!' });
:::
参数可以是任何类型,只要它们实现 serde::Deserialize
。
¥Arguments can be of any type, as long as they implement serde::Deserialize
.
相应的 JavaScript:
¥The corresponding JavaScript:
invoke('my_custom_command', { invoke_message: 'Hello!' });
返回数据
¥Returning Data
命令处理程序也可以返回数据:
¥Command handlers can return data as well:
#[tauri::command]fn my_custom_command() -> String { "Hello from Rust!".into()}
invoke
函数返回一个使用返回值解析的promise:
¥The invoke
function returns a promise that resolves with the returned value:
invoke('my_custom_command').then((message) => console.log(message));
返回的数据可以是任何类型,只要它实现 serde::Serialize
。
¥Returned data can be of any type, as long as it implements serde::Serialize
.
返回数组缓冲区
¥Returning Array Buffers
当响应发送到前端时,实现 serde::Serialize
的返回值将被序列化为 JSON。如果你尝试返回大量数据(例如文件或下载 HTTP 响应),这可能会减慢你的应用速度。要以优化的方式返回数组缓冲区,请使用 tauri::ipc::Response
:
¥Return values that implements serde::Serialize
are serialized to JSON when the response is sent to the frontend.
This can slow down your application if you try to return a large data such as a file or a download HTTP response.
To return array buffers in an optimized way, use tauri::ipc::Response
:
use tauri::ipc::Response;#[tauri::command]fn read_file() -> Response { let data = std::fs::read("/path/to/file").unwrap(); tauri::ipc::Response::new(data)}
错误处理
¥Error Handling
如果你的处理程序可能失败并且需要能够返回错误,请让函数返回 Result
:
¥If your handler could fail and needs to be able to return an error, have the function return a Result
:
#[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将拒绝,否则,它将解析:
¥If the command returns an error, the promise will reject, otherwise, it resolves:
invoke('login', { user: 'tauri', password: '0j4rijw8=' }) .then((message) => console.log(message)) .catch((error) => console.error(error));
如上所述,从命令返回的所有内容都必须实现 serde::Serialize
,包括错误。如果你使用 Rust 的 std 库或外部包中的错误类型,这可能会有问题,因为大多数错误类型都没有实现它。在简单情况下,你可以使用 map_err
将这些错误转换为 String
:
¥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:
#[tauri::command]fn my_custom_command() -> Result<(), String> { std::fs::File::open("path/to/file").map_err(|err| err.to_string())?; // Return `null` on success Ok(())}
由于这不是很惯用,你可能希望创建自己的实现 serde::Serialize
的错误类型。在下面的例子中,我们使用 thiserror
包来帮助创建错误类型。它允许你通过派生 thiserror::Error
特性将枚举转换为错误类型。你可以查阅其文档以了解更多详细信息。
¥Since this is not very idiomatic you may want to create your own error type which implements serde::Serialize
.
In the following example, we use the thiserror
crate to help create the error type.
It allows you to turn enums into error types by deriving the thiserror::Error
trait.
You can consult its documentation for more details.
// 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::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> { // This will return an error std::fs::File::open("path/that/does/not/exist")?; // Return `null` on success Ok(())}
自定义错误类型的优点是可以使所有可能的错误都明确,因此读者可以快速识别可能发生的错误。这为其他人(和你自己)节省了大量时间,以便以后审查和重构代码。
它还让你完全控制错误类型的序列化方式。在上面的例子中,我们只是将错误消息作为字符串返回,但你可以为每个错误分配一个代码,以便更轻松地将其映射到类似的 TypeScript 错误枚举,例如:
¥A custom error type has the advantage of making all possible errors explicit so readers can quickly identify what errors can happen.
This saves other people (and yourself) enormous amounts of time when reviewing and refactoring code later.
It also gives you full control over the way your error type gets serialized.
In the above example, we simply returned the error message as a string, but you could assign each error a code
so you could more easily map it to a similar looking TypeScript error enum for example:
#[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 }
错误对象:
¥In your frontend you now get a { kind: 'io' | 'utf8', message: string }
error object:
type ErrorKind = { kind: 'io' | 'utf8'; message: string;};
invoke('read').catch((e: ErrorKind) => {});
异步命令
¥Async Commands
Tauri 中首选异步命令,以便以不会导致 UI 冻结或减速的方式执行繁重的工作。
¥Asynchronous commands are preferred in Tauri to perform heavy work in a manner that doesn’t result in UI freezes or slowdowns.
:::note 注意
使用 async_runtime::spawn
在单独的异步任务上执行异步命令。除非使用 #[tauri::command(async)] 定义,否则没有 async 关键字的命令将在主线程上执行。
¥Async commands are executed on a separate async task using async_runtime::spawn
.
Commands without the async keyword are executed on the main thread unless defined with #[tauri::command(async)].
:::
如果你的命令需要异步运行,只需将其声明为 async
。
¥If your command needs to run asynchronously, simply declare it as async
.
:::caution 提醒
使用 Tauri 创建异步函数时需要小心谨慎。目前,你不能简单地在异步函数的签名中包含借用的参数。此类类型的一些常见示例是 &str
和 State<'_, Data>
。此限制在此处跟踪:https://github.com/tauri-apps/tauri/issues/2533 和解决方法如下所示。
¥You need to be careful when creating asynchronous functions using Tauri.
Currently, you cannot simply include borrowed arguments in the signature of an asynchronous function.
Some common examples of types like this are &str
and State<'_, Data>
.
This limitation is tracked here: https://github.com/tauri-apps/tauri/issues/2533 and workarounds are shown below.
:::
使用借用类型时,你必须进行其他更改。这些是你的两个主要选项:
¥When working with borrowed types, you have to make additional changes. These are your two main options:
选项 1:将类型(例如 &str
)转换为未借用的类似类型(例如 String
)。这可能不适用于所有类型,例如 State<'_, Data>
。
¥Option 1: Convert the type, such as &str
to a similar type that is not borrowed, such as String
.
This may not work for all types, for example State<'_, Data>
.
示例:
¥Example:
// 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; value}
选项 2:将返回类型封装在 Result
中。这个有点难以实现,但适用于所有类型。
¥Option 2: Wrap the return type in a Result
. This one is a bit harder to implement, but works for all types.
使用返回类型 Result<a, b>
,将 a
替换为你希望返回的类型,或如果你希望返回 null
,则替换为 ()
,并将 b
替换为错误类型以在出现问题时返回,或如果你希望不返回可选错误,则替换为 ()
。例如:
¥Use the return type Result<a, b>
, replacing a
with the type you wish to return, or ()
if you wish to return null
, and replacing b
with an error type to return if something goes wrong, or ()
if you wish to have no optional error returned. For example:
-
Result<String, ()>
返回一个字符串,没有错误。¥
Result<String, ()>
to return a String, and no error. -
Result<(), ()>
返回null
。¥
Result<(), ()>
to returnnull
. -
Result<bool, Error>
返回一个布尔值或错误,如上面的 错误处理 部分所示。¥
Result<bool, Error>
to return a boolean or an error as shown in the Error Handling section above.
示例:
¥Example:
// 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))}
从 JavaScript 调用
¥Invoking from JavaScript
由于从 JavaScript 调用命令已经返回一个promise,因此它的工作方式与任何其他命令一样:
¥Since invoking the command from JavaScript already returns a promise, it works just like any other command:
invoke('my_custom_command', { value: 'Hello, Async!' }).then(() => console.log('Completed!'));
通道
¥Channels
Tauri 通道是推荐的流式传输数据(例如流式传输的 HTTP 响应到前端)的机制。以下示例读取文件并以 4096 字节的块形式通知前端进度:
¥The Tauri channel is the recommended mechanism for streaming data such as streamed HTTP responses to the frontend. The following example reads a file and notifies the frontend of the progress in chunks of 4096 bytes:
use tokio::io::AsyncReadExt;
#[tauri::command]async fn load_image(path: std::path::PathBuf, reader: tauri::ipc::Channel<&[u8]>) { // for simplicity this example does not include error handling 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 { // Length of zero means end of file. break; } reader.send(&chunk).unwrap(); }}
有关更多信息,请参阅 通道文档。
¥See the channels documentation for more information.
在命令中访问 WebviewWindow
¥Accessing the WebviewWindow in Commands
命令可以访问调用消息的 WebviewWindow
实例:
¥Commands can access the WebviewWindow
instance that invoked the message:
#[tauri::command]async fn my_custom_command(webview_window: tauri::WebviewWindow) { println!("WebviewWindow: {}", webview_window.label());}
在命令中访问 AppHandle
¥Accessing an AppHandle in Commands
命令可以访问 AppHandle
实例:
¥Commands can access an AppHandle
instance:
#[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 || {});}
访问托管状态
¥Accessing Managed State
Tauri 可以使用 tauri::Builder
上的 manage
函数管理状态。可以使用 tauri::State
在命令上访问状态:
¥Tauri can manage state using the manage
function on tauri::Builder
.
The state can be accessed on a command using 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");}
访问原始请求
¥Accessing Raw Request
Tauri 命令还可以访问完整的 tauri::ipc::Request
对象,其中包括原始主体负载和请求标头。
¥Tauri commands can also access the full tauri::ipc::Request
object which includes the raw body payload and the request headers.
#[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,并在第三个参数中包含请求标头:
¥In the frontend you can call invoke() sending a raw request body by providing an ArrayBuffer or Uint8Array on the payload argument, and include request headers in the third argument:
const data = new Uint8Array([1, 2, 3]);await __TAURI__.core.invoke('upload', data, { headers: { Authorization: 'apikey', },});
创建多个命令
¥Creating Multiple Commands
tauri::generate_handler!
宏接受一组命令。要注册多个命令,你不能多次调用 invoke_handler。只会使用最后一次调用。你必须将每个命令传递给 tauri::generate_handler!
的一次调用。
¥The tauri::generate_handler!
macro takes an array of commands. To register
multiple commands, you cannot call invoke_handler multiple times. Only the last
call will be used. You must pass each command to a single call of
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");}
完整示例
¥Complete Example
上述任何或所有功能都可以组合使用:
¥Any or all of the above features can be combined:
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';
// Invocation from JavaScriptinvoke('my_custom_command', { number: 42,}) .then((res) => console.log(`Message: ${res.message}, Other Val: ${res.other_val}`) ) .catch((e) => console.error(e));
事件系统
¥Event System
事件系统是前端和 Rust 之间更简单的通信机制。与命令不同,事件不是类型安全的,始终是异步的,无法返回值并且仅支持 JSON 负载。
¥The event system is a simpler communication mechanism between your frontend and the Rust. Unlike commands, events are not type safe, are always async, cannot return values and only supports JSON payloads.
全球事件
¥Global Events
要触发全局事件,你可以使用 event.emit 或 WebviewWindow#emit 函数:
¥To trigger a global event you can use the event.emit or the WebviewWindow#emit functions:
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 注意
全局事件传递给所有监听器
¥Global events are delivered to all listeners
:::
Webview 事件
¥Webview Event
要触发特定 webview 注册的监听器事件,你可以使用 event.emitTo 或 WebviewWindow#emitTo 函数:
¥To trigger an event to a listener registered by a specific webview you can use the event.emitTo or the WebviewWindow#emitTo functions:
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' } }
选项,该选项将监听器定义为触发事件的捕获器:
¥Webview-specific events are not triggered to regular global event listeners.
To listen to any event you must provide the { target: { kind: 'Any' } }
option to the event.listen function,
which defines the listener to act as a catch-all for emitted events:
import { listen } from '@tauri-apps/api/event';listen( 'state-changed', (event) => { console.log('got state changed event', event); }, { target: { kind: 'Any' }, });
:::
监听事件
¥Listening to Events
@tauri-apps/api
NPM 包提供 API 来监听全局和特定于 webview 的事件。
¥The @tauri-apps/api
NPM package offers APIs to listen to both global and webview-specific events.
-
监听全局事件
¥Listening to global events
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 的事件
¥Listening to webview-specific events
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
函数:
¥The listen
function keeps the event listener registered for the entire lifetime of the application.
To stop listening on an event you can use the unlisten
function which is returned by the listen
function:
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('download-started', (event) => {});unlisten();
:::note 注意
当执行上下文超出范围(例如卸载组件时)时,请始终使用 unlisten 函数。
¥Always use the unlisten function when your execution context goes out of scope such as when a component is unmounted.
当重新加载页面或你导航到另一个 URL 时,监听器将自动取消注册。但这不适用于单页应用 (SPA) 路由。
¥When the page is reloaded or you navigate to another URL the listeners are unregistered automatically. This does not apply to a Single Page Application (SPA) router though.
:::
此外,Tauri 还提供了一个实用函数,用于精确监听一次事件:
¥Additionally Tauri provides a utility function for listening to an event exactly once:
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 文档。
¥Events emitted in the frontend also triggers listeners registed by these APIs. For more information see the Calling Rust from the Frontend documentation.
:::
监听 Rust 上的事件
¥Listening to Events on Rust
全局和特定于 webview 的事件也会传递给在 Rust 中注册的监听器。
¥Global and webview-specific events are also delivered to listeners registered in Rust.
-
监听全局事件
¥Listening to global events
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 的事件
¥Listening to webview-specific events
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
函数:
¥The listen
function keeps the event listener registered for the entire lifetime of the application.
To stop listening on an event you can use the unlisten
function:
// unlisten outside of the event handler scope:let event_id = app.listen("download-started", |event| {});app.unlisten(event_id);
// unlisten when some event criteria is matchedlet handle = app.handle().clone();app.listen("status-changed", |event| { if event.data == "ready" { handle.unlisten(event.id); }});
此外,Tauri 还提供了一个实用函数,用于精确监听一次事件:
¥Additionally Tauri provides a utility function for listening to an event exactly once:
app.once("ready", |event| { println!("app is ready");});
在这种情况下,事件监听器在第一次触发后立即取消注册。
¥In this case the event listener is immediately unregistered after its first trigger.
要了解如何从 Rust 代码中监听事件和触发事件,请参阅 Rust 事件系统文档。
¥To learn how to listen to events and emit events from your Rust code, see the Rust Event System documentation.
Tauri 中文网 - 粤ICP备13048890号