Skip to content

从前端调用 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 提供了一个用于以类型安全的方式调用 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 系统,用于从你的网络应用调用 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]:

src-tauri/src/lib.rs
#[tauri::command]
fn my_custom_command() {
println!("I was invoked from JavaScript!");
}

你将必须像这样向构建函数提供你的命令列表:

🌐 You will have to provide a list of your commands to the builder function like so:

src-tauri/src/lib.rs
#[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 true
const invoke = window.__TAURI__.core.invoke;
// Invoke the command
invoke('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:

src-tauri/src/commands.rs
#[tauri::command]
pub fn my_custom_command() {
println!("I was invoked from JavaScript!");
}

lib.rs 文件中,定义模块并相应地提供你的命令列表;

🌐 In the lib.rs file, define the module and provide the list of your commands accordingly;

src-tauri/src/lib.rs
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.

当使用 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);
}

参数应该作为带有驼峰式键的 JSON 对象传递:

🌐 Arguments should be passed as a JSON object with camelCase keys:

invoke('my_custom_command', { invokeMessage: 'Hello!' });

参数可以是任何类型,只要它们实现了 serde::Deserialize

🌐 Arguments can be of any type, as long as they implement serde::Deserialize.

🌐 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 的标准库或外部包中的错误类型,这可能会有问题,因为大多数错误类型并未实现它。在简单的场景中,你可以使用 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:

#[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 crate 来帮助创建错误类型。 它允许你通过派生 thiserror::Error trait 将枚举转换为错误类型。 你可以查阅它的文档以获取更多详细信息。

🌐 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::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 `null` on success
Ok(())
}

自定义错误类型的优点在于,它可以将所有可能的错误明确列出,因此读者可以快速识别可能发生的错误。这为其他人(以及你自己)在日后审查和重构代码时节省了大量时间。
它还让你完全控制错误类型的序列化方式。在上述示例中,我们只是将错误信息作为字符串返回,但你可以为每个错误分配一个代码,这样你就可以更容易地将其映射到类似的 TypeScript 错误枚举,例如:

#[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 中更推荐使用异步命令来执行繁重的工作,以避免导致用户界面冻结或变慢。

🌐 Asynchronous commands are preferred in Tauri to perform heavy work in a manner that doesn’t result in UI freezes or slowdowns.

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

在使用借用类型时,你必须进行额外的更改。这是你的两个主要选项:

🌐 When working with borrowed types, you have to make additional changes. These are your two main options:

选项 1:转换类型,例如将 &str 转换为类似的非借用类型,例如 String
这可能并不适用于所有类型,例如 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 中。这个实现起来有点难,但适用于所有类型。

使用返回类型 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<(), ()> 返回 null
  • Result<bool, Error> 将返回一个布尔值或一个错误,如上面的 错误处理 部分所示。

示例:

🌐 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))
}

🌐 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.

🌐 Accessing the WebviewWindow in Commands

命令可以访问调用该消息的 WebviewWindow 实例:

🌐 Commands can access the WebviewWindow instance that invoked the message:

src-tauri/src/lib.rs
#[tauri::command]
async fn my_custom_command(webview_window: tauri::WebviewWindow) {
println!("WebviewWindow: {}", webview_window.label());
}

🌐 Accessing an AppHandle in Commands

命令可以访问一个 AppHandle 实例:

🌐 Commands can access an AppHandle instance:

src-tauri/src/lib.rs
#[tauri::command]
async fn my_custom_command(app_handle: tauri::AppHandle) {
let app_dir = app_handle.path().app_dir();
use tauri::GlobalShortcutManager;
app_handle.global_shortcut_manager().register("CTRL + U", move || {});
}

🌐 Accessing Managed State

Tauri 可以使用 manage 函数在 tauri::Builder 上管理状态。可以在命令中使用 tauri::State 访问该状态:

🌐 Tauri can manage state using the manage function on tauri::Builder. The state can be accessed on a command using tauri::State:

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");
}

🌐 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!.

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");
}

🌐 Complete Example

以上任意或所有功能均可组合:

🌐 Any or all of the above features can be combined:

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::WebviewWindow,
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 JavaScript
invoke('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.emitWebviewWindow#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 });

🌐 Webview Event

要向由特定 Webview 注册的监听器触发事件,你可以使用 event.emitToWebviewWindow#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',
});

🌐 Listening to Events

@tauri-apps/api NPM 包提供了用于监听全局事件和特定 webview 事件的 API。

🌐 The @tauri-apps/api NPM package offers APIs to listen to both global and webview-specific 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 特定事件

    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();

🌐 Common Pitfalls

在监听器解析之前不要调用 unlisten()
Section titled “在监听器解析之前不要调用 unlisten()”

🌐 Don’t call unlisten() before the listener resolves

listen 函数返回一个 Promise,该 Promise 会解析为 unlisten 句柄。
如果在 Promise 解析之前同步调用 unlisten,处理程序将会立即被移除,你将不会收到任何事件:

🌐 The listen function returns a Promise that resolves to the unlisten handle. If you call unlisten synchronously before the Promise resolves, the handler will be removed immediately and you won’t receive any events:

// Wrong: unlisten is called before the listener is registered
const unlisten = listen('sync-complete', (event) => {
console.log('sync finished');
});
unlisten(); // unlisten is a Promise here, not a function -- the listener is not cleaned up
// Correct: await the Promise to get the unlisten handle
const unlisten = await listen('sync-complete', (event) => {
console.log('sync finished');
});
// Now you can store and call it later, e.g. in a cleanup function
unlisten();

🌐 Timing in setup hooks

在像 React、Vue 和 Svelte 这样的框架中,setup 或 mount 钩子会在组件完全渲染之前运行。如果你在 setup 期间监听事件,请确保事件处理程序不依赖尚未渲染的 DOM 元素,或者将监听器注册推迟到在挂载后运行的 effect/钩子中。

🌐 In frameworks like React, Vue, and Svelte, the setup or mount hook runs before the component is fully rendered. If you listen for events during setup, make sure the event handler does not depend on DOM elements that haven’t been rendered yet, or defer the listener registration to an effect/hook that runs after mount.

// Wrong: DOM ref may not be available yet
function MyComponent() {
const ref = useRef(null);
listen('scroll-to', (event) => {
ref.current.scrollIntoView(); // ref.current may be null during setup
});
return <div ref={ref} />;
}
// Correct: use useEffect which runs after the component mounts
function MyComponent() {
const ref = useRef(null);
useEffect(() => {
const unlisten = listen('scroll-to', (event) => {
ref.current?.scrollIntoView();
});
return () => {
unlisten.then((fn) => fn());
};
}, []);
return <div ref={ref} />;
}

🌐 Event ordering and async listeners

事件监听器会按照注册的顺序被调用,但如果一个监听器是异步的,并且事件触发器在短时间内发送多个事件,监听器可能会乱序处理事件。对于有序、高吞吐量的数据传输,可以考虑使用 Channels 而不是事件系统。

🌐 Event listeners are called in the order they are registered, but if a listener is async and the event emitter sends multiple events in rapid succession, the listeners may process events out of order. For ordered, high-throughput data delivery, consider using Channels instead of the event system.

🌐 Framework-specific Cleanup Examples

在使用前端框架时,当组件被卸载时应该清理事件监听器,以避免内存泄漏和重复的处理程序。

🌐 When using a frontend framework, you should clean up event listeners when a component is unmounted to avoid memory leaks and duplicate handlers.

import { useEffect, useState } from 'react';
import { listen } from '@tauri-apps/api/event';
function DownloadTracker() {
const [progress, setProgress] = useState(0);
useEffect(() => {
const unlisten = listen<number>('download-progress', (event) => {
setProgress(event.payload);
});
return () => {
unlisten.then((fn) => fn());
};
}, []);
return <div>Download progress: {progress}%</div>;
}

此外,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', () => {});

🌐 Listening to Events on Rust

全局和特定于 webview 的事件也会传递给在 Rust 中注册的监听器。

🌐 Global and webview-specific events are also delivered to listeners registered in Rust.

  • 聆听全球事件

    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 特定事件

    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 matched
let 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号
Nodejs.cn 旗下网站