calling-frontend-from-tauri-rust
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCalling Frontend from Tauri Rust
从Tauri Rust调用前端
Tauri provides three mechanisms for Rust to communicate with the frontend: the event system, channels, and JavaScript evaluation.
Tauri为Rust与前端通信提供了三种机制:事件系统、通道以及JavaScript执行。
Event System Overview
事件系统概述
The event system enables bi-directional communication between Rust and frontend. Best for small data transfers and multi-consumer patterns. Not designed for low latency or high throughput.
事件系统支持Rust与前端之间的双向通信。最适合小数据传输和多消费者场景,不针对低延迟或高吞吐量设计。
Required Imports
必要导入
rust
use tauri::{AppHandle, Emitter, Manager, Listener, EventTarget};
use serde::Serialize;typescript
import { listen, once, emit } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';rust
use tauri::{AppHandle, Emitter, Manager, Listener, EventTarget};
use serde::Serialize;typescript
import { listen, once, emit } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';Emitting Events from Rust
从Rust发送事件
Global Events (All Listeners)
全局事件(所有监听器)
Use to broadcast to all listeners:
AppHandle::emit()rust
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();
}使用向所有监听器广播事件:
AppHandle::emit()rust
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();
}Webview-Specific Events
特定Webview事件
Target specific webviews with :
emit_to()rust
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();
}使用指定目标Webview:
emit_to()rust
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();
}Filtered Events (Multiple Webviews)
过滤事件(多个Webview)
Use for conditional targeting:
emit_filter()rust
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();
}使用进行条件目标定位:
emit_filter()rust
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();
}Event Payloads
事件负载
Custom payloads must implement and :
SerializeClonerust
use serde::Serialize;
use tauri::{AppHandle, Emitter};
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct DownloadProgress {
download_id: usize,
chunk_length: usize,
total_size: usize,
}
#[tauri::command]
fn download(app: AppHandle, url: String) {
app.emit("download-progress", DownloadProgress {
download_id: 1,
chunk_length: 150,
total_size: 1000,
}).unwrap();
}自定义负载必须实现和 trait:
SerializeClonerust
use serde::Serialize;
use tauri::{AppHandle, Emitter};
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct DownloadProgress {
download_id: usize,
chunk_length: usize,
total_size: usize,
}
#[tauri::command]
fn download(app: AppHandle, url: String) {
app.emit("download-progress", DownloadProgress {
download_id: 1,
chunk_length: 150,
total_size: 1000,
}).unwrap();
}Listening in Frontend
前端监听事件
Global Event Listeners
全局事件监听器
typescript
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}`);
});typescript
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-Specific Listeners
特定Webview监听器
typescript
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWebview = getCurrentWebviewWindow();
appWebview.listen<string>('logged-in', (event) => {
localStorage.setItem('session-token', event.payload);
});typescript
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWebview = getCurrentWebviewWindow();
appWebview.listen<string>('logged-in', (event) => {
localStorage.setItem('session-token', event.payload);
});Managing Listeners
管理监听器
typescript
import { listen, once } from '@tauri-apps/api/event';
// Unlisten to prevent memory leaks
const unlisten = await listen('download-started', (event) => {
console.log('download started');
});
unlisten(); // Stop listening when done
// Listen once for one-time events
once('app-ready', (event) => {
console.log('App is ready:', event.payload);
});typescript
import { listen, once } from '@tauri-apps/api/event';
// 取消监听以避免内存泄漏
const unlisten = await listen('download-started', (event) => {
console.log('download started');
});
unlisten(); // 使用完毕后停止监听
// 单次监听一次性事件
once('app-ready', (event) => {
console.log('App is ready:', event.payload);
});Listening in Rust
Rust监听事件
Global and Webview Listeners
全局与Webview监听器
rust
use tauri::{Listener, Manager};
tauri::Builder::default()
.setup(|app| {
// Global listener
app.listen("download-started", |event| {
println!("event received: {}", event.payload());
});
// Webview-specific listener
let webview = app.get_webview_window("main").unwrap();
webview.listen("logged-in", |event| {
println!("User logged in: {}", event.payload());
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application")rust
use tauri::{Listener, Manager};
tauri::Builder::default()
.setup(|app| {
// 全局监听器
app.listen("download-started", |event| {
println!("event received: {}", event.payload());
});
// 特定Webview监听器
let webview = app.get_webview_window("main").unwrap();
webview.listen("logged-in", |event| {
println!("User logged in: {}", event.payload());
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application")Unlisten and Listen Once
取消监听与单次监听
rust
use tauri::Listener;
// Store event ID to unlisten later
let event_id = app.listen("download-started", |event| {
println!("download started");
});
app.unlisten(event_id);
// Conditional unlisten
let handle = app.handle().clone();
app.listen("status-changed", move |event| {
if event.payload() == "\"ready\"" {
handle.unlisten(event.id());
}
});
// Listen once
app.once("ready", |event| {
println!("app is ready: {}", event.payload());
});rust
use tauri::Listener;
// 存储事件ID以便后续取消监听
let event_id = app.listen("download-started", |event| {
println!("download started");
});
app.unlisten(event_id);
// 条件取消监听
let handle = app.handle().clone();
app.listen("status-changed", move |event| {
if event.payload() == "\"ready\"" {
handle.unlisten(event.id());
}
});
// 单次监听
app.once("ready", |event| {
println!("app is ready: {}", event.payload());
});Channels (High-Throughput Streaming)
通道(高吞吐量流传输)
For better performance than events, use channels:
若需要比事件更好的性能,可使用通道:
Rust Channel Setup
Rust通道设置
rust
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>) {
on_event.send(DownloadEvent::Started {
url: &url,
download_id: 1,
content_length: 1000,
}).unwrap();
for _ in 0..10 {
on_event.send(DownloadEvent::Progress {
download_id: 1,
chunk_length: 100,
}).unwrap();
}
on_event.send(DownloadEvent::Finished { download_id: 1 }).unwrap();
}rust
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>) {
on_event.send(DownloadEvent::Started {
url: &url,
download_id: 1,
content_length: 1000,
}).unwrap();
for _ in 0..10 {
on_event.send(DownloadEvent::Progress {
download_id: 1,
chunk_length: 100,
}).unwrap();
}
on_event.send(DownloadEvent::Finished { download_id: 1 }).unwrap();
}Frontend Channel Usage
前端通道使用
typescript
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) => {
switch (message.event) {
case 'started':
console.log(`Download started: ${message.data.url}`);
break;
case 'progress':
console.log(`Progress: ${message.data.chunkLength} bytes`);
break;
case 'finished':
console.log('Download complete!');
break;
}
};
await invoke('download', { url: 'https://example.com/file.json', onEvent });typescript
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) => {
switch (message.event) {
case 'started':
console.log(`Download started: ${message.data.url}`);
break;
case 'progress':
console.log(`Progress: ${message.data.chunkLength} bytes`);
break;
case 'finished':
console.log('Download complete!');
break;
}
};
await invoke('download', { url: 'https://example.com/file.json', onEvent });JavaScript Evaluation
JavaScript执行
Execute JavaScript directly from Rust:
从Rust直接执行JavaScript:
Basic Evaluation
基础执行
rust
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
use tauri::Manager;
tauri::Builder::default()
.setup(|app| {
let webview = app.get_webview_window("main").unwrap();
webview.eval("console.log('hello from Rust')")?;
Ok(())
})Evaluation with Data
带数据的执行
rust
use tauri::Manager;
#[tauri::command]
fn notify_frontend(app: tauri::AppHandle, message: String) {
if let Some(webview) = app.get_webview_window("main") {
let script = format!("window.showNotification('{}')", message);
webview.eval(&script).unwrap();
}
}rust
use tauri::Manager;
#[tauri::command]
fn notify_frontend(app: tauri::AppHandle, message: String) {
if let Some(webview) = app.get_webview_window("main") {
let script = format!("window.showNotification('{}')", message);
webview.eval(&script).unwrap();
}
}Complex Data with serialize-to-javascript
使用serialize-to-javascript处理复杂数据
toml
undefinedtoml
undefinedCargo.toml
Cargo.toml
[dependencies]
serialize-to-javascript = "0.1"
```rust
use serialize_to_javascript::Serialized;
use tauri::Manager;
#[derive(serde::Serialize)]
struct AppState { user: String, logged_in: bool }
#[tauri::command]
fn sync_state(app: tauri::AppHandle) {
let state = AppState { user: "john".to_string(), logged_in: true };
if let Some(webview) = app.get_webview_window("main") {
let serialized = Serialized::new(&state, &Default::default()).into_string();
webview.eval(&format!("window.updateState({})", serialized)).unwrap();
}
}[dependencies]
serialize-to-javascript = "0.1"
```rust
use serialize_to_javascript::Serialized;
use tauri::Manager;
#[derive(serde::Serialize)]
struct AppState { user: String, logged_in: bool }
#[tauri::command]
fn sync_state(app: tauri::AppHandle) {
let state = AppState { user: "john".to_string(), logged_in: true };
if let Some(webview) = app.get_webview_window("main") {
let serialized = Serialized::new(&state, &Default::default()).into_string();
webview.eval(&format!("window.updateState({})", serialized)).unwrap();
}
}Choosing the Right Method
选择合适的方法
| Method | Use Case | Performance |
|---|---|---|
Events ( | Multi-consumer, broadcast | Moderate |
| Channels | High-throughput streaming, single consumer | High |
| JS Eval | Direct DOM manipulation, no response needed | Low overhead |
Events: Notifying multiple windows, loose coupling, simple status updates.
Channels: File downloads/uploads with progress, real-time streaming, high-frequency updates.
JS Eval: One-off DOM updates, triggering frontend functions directly.
| 方法 | 使用场景 | 性能 |
|---|---|---|
事件( | 多消费者、广播 | 中等 |
| 通道 | 高吞吐量流传输、单消费者 | 高 |
| JS执行 | 直接DOM操作、无需响应 | 低开销 |
事件:通知多个窗口、松耦合、简单状态更新。
通道:带进度的文件下载/上传、实时流传输、高频更新。
JS执行:一次性DOM更新、直接触发前端函数。
Complete Example: File Watcher
完整示例:文件监视器
Rust Side
Rust端
rust
use tauri::{AppHandle, Emitter};
use serde::Serialize;
use std::path::PathBuf;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct FileChange { path: String, event_type: String }
#[tauri::command]
fn watch_directory(app: AppHandle, path: PathBuf) {
std::thread::spawn(move || {
loop {
app.emit("file-changed", FileChange {
path: path.to_string_lossy().to_string(),
event_type: "modified".to_string(),
}).unwrap();
std::thread::sleep(std::time::Duration::from_secs(5));
}
});
}rust
use tauri::{AppHandle, Emitter};
use serde::Serialize;
use std::path::PathBuf;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct FileChange { path: String, event_type: String }
#[tauri::command]
fn watch_directory(app: AppHandle, path: PathBuf) {
std::thread::spawn(move || {
loop {
app.emit("file-changed", FileChange {
path: path.to_string_lossy().to_string(),
event_type: "modified".to_string(),
}).unwrap();
std::thread::sleep(std::time::Duration::from_secs(5));
}
});
}Frontend Side
前端端
typescript
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
type FileChange = { path: string; eventType: string };
await invoke('watch_directory', { path: '/some/directory' });
const unlisten = await listen<FileChange>('file-changed', (event) => {
console.log(`File ${event.payload.eventType}: ${event.payload.path}`);
});
// Cleanup when component unmounts: unlisten();typescript
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
type FileChange = { path: string; eventType: string };
await invoke('watch_directory', { path: '/some/directory' });
const unlisten = await listen<FileChange>('file-changed', (event) => {
console.log(`File ${event.payload.eventType}: ${event.payload.path}`);
});
// 组件卸载时清理:unlisten();