从 Rust 调用前端
本文档包含有关如何从 Rust 代码与应用前端进行通信的指南。要了解如何从前端与你的 Rust 代码通信,请参阅 从前端调用 Rust。
¥This document includes guides on how to communicate with your application frontend from your Rust code. To see how to communicate with your Rust code from your frontend, see Calling Rust from the Frontend.
Tauri 应用的 Rust 端可以通过利用 Tauri 事件系统、使用通道或直接评估 JavaScript 代码来调用前端。
¥The Rust side of your Tauri application can call the frontend by leveraging the Tauri event system, using channels or directly evaluating JavaScript code.
事件系统
¥Event System
Tauri 提供了一个简单的事件系统,你可以使用它在 Rust 和前端之间进行双向通信。
¥Tauri ships a simple event system you can use to have bi-directional communication between Rust and your frontend.
事件系统设计用于需要流式传输少量数据或需要实现多消费者多生产者模式(例如推送通知系统)的情况。
¥The event system was designed for situations where small amounts of data need to be streamed or you need to implement a multi consumer multi producer pattern (e.g. push notification system).
事件系统不适用于低延迟或高吞吐量情况。有关自定义选项的完整列表,请参阅 通道部分。
¥The event system is not designed for low latency or high throughput situations. See the channels section for the implementation optimized for streaming data.
Tauri 命令和 Tauri 事件之间的主要区别在于事件没有强类型支持,事件有效负载始终是 JSON 字符串,这使得它们不适合更大的消息,并且 capabilities 系统不支持细粒度控制事件数据和通道。
¥The major differences between a Tauri command and a Tauri event are that events have no strong type support, event payloads are always JSON strings making them not suitable for bigger messages and there is no support of the capabilities system to fine grain control event data and channels.
AppHandle 和 WebviewWindow 类型实现事件系统特性 Listener 和 触发器。
¥The AppHandle and WebviewWindow types implement the event system traits Listener and Emitter.
事件要么是全局的(传递给所有监听器),要么是特定于 webview 的(仅传递给与给定标签匹配的 webview)。
¥Events are either global (delivered to all listeners) or webview-specific (only delivered to the webview matching a given label).
全球事件
¥Global Events
要触发全局事件,你可以使用 Emitter#emit 函数:
¥To trigger a global event you can use the Emitter#emit function:
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 注意
全局事件传递给所有监听器
¥Global events are delivered to all listeners
:::
Webview 事件
¥Webview Event
要触发特定 webview 注册的监听器事件,你可以使用 Emitter#emit_to 函数:
¥To trigger an event to a listener registered by a specific webview you can use the Emitter#emit_to function:
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 列表的事件。在下面的例子中,我们向主和文件查看器 webviews 发出一个打开文件事件:
¥It is also possible to trigger an event to a list of webviews by calling Emitter#emit_filter. In the following example we emit a open-file event to the main and file-viewer webviews:
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
,该函数将监听器定义为触发事件的捕获器。
¥Webview-specific events are not triggered to regular global event listeners.
To listen to any event you must use the listen_any
function instead of listen
,
which defines the listener to act as a catch-all for emitted events.
:::
事件有效负载
¥Event Payload
事件有效负载可以是任何 serializable 类型,也可以实现 克隆。让我们通过使用对象在每个事件中发出更多信息来增强下载事件示例:
¥The event payload can be any serializable type that also implements Clone. Let’s enhance the download event example by using an object to emit more information in each event:
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();}
监听事件
¥Listening to Events
Tauri 提供 API 来监听 Web 视图和 Rust 接口上的事件。
¥Tauri provides APIs to listen to events on both the webview and the Rust interfaces.
监听前端上的事件
¥Listening to Events on the Frontend
@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.
通道
¥Channels
事件系统旨在成为一种简单的双向通信,可在你的应用中全局使用。在底层,它直接评估 JavaScript 代码,因此可能不适合发送大量数据。
¥The event system is designed to be a simple two way communication that is globally available in your application. Under the hood it directly evaluates JavaScript code so it might not be suitable to sending a large amount of data.
通道旨在快速并提供有序数据。它们在内部用于流式传输操作,例如下载进度、子进程输出和 WebSocket 消息。
¥Channels are designed to be fast and deliver ordered data. They are used internally for streaming operations such as download progress, child process output and WebSocket messages.
让我们重写我们的下载命令示例以使用通道而不是事件系统:
¥Let’s rewrite our download command example to use channels instead of the event system:
use tauri::{AppHandle, ipc::Channel};use serde::Serialize;
#[derive(Clone, Serialize)]#[serde(rename_all = "camelCase", tag = "event", content = "data")]enum DownloadEvent<'a> { #[serde(rename_all = "camelCase")] Started { url: &'a str, download_id: usize, content_length: usize, }, #[serde(rename_all = "camelCase")] Progress { download_id: usize, chunk_length: usize, }, #[serde(rename_all = "camelCase")] 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();}
调用下载命令时,你必须创建通道并将其作为参数提供:
¥When calling the download command you must create the channel and provide it as an argument:
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
¥Evaluating JavaScript
要在 webview 上下文中直接执行任何 JavaScript 代码,你可以使用 WebviewWindow#eval
函数:
¥To directly execute any JavaScript code on the webview context you can use the WebviewWindow#eval
function:
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 包。
¥If the script to be evaluated is not so simple and must use input from Rust objects we recommend using the serialize-to-javascript crate.
Tauri 中文网 - 粤ICP备13048890号