calling-frontend-from-tauri-rust

安装量: 43
排名: #16961

安装

npx skills add https://github.com/dchuk/claude-code-tauri-skills --skill calling-frontend-from-tauri-rust

Calling Frontend from Tauri Rust

Tauri provides three mechanisms for Rust to communicate with the frontend: the event system, channels, and JavaScript evaluation.

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.

Required Imports use tauri::{AppHandle, Emitter, Manager, Listener, EventTarget}; use serde::Serialize;

import { listen, once, emit } from '@tauri-apps/api/event'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';

Emitting Events from Rust Global Events (All Listeners)

Use AppHandle::emit() to broadcast to all listeners:

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

Target specific webviews with emit_to():

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)

Use emit_filter() for conditional targeting:

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 Serialize and Clone:

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 import { listen } from '@tauri-apps/api/event';

type DownloadStarted = { url: string; downloadId: number; contentLength: number; };

listen('download-started', (event) => { console.log(downloading ${event.payload.contentLength} bytes from ${event.payload.url}); });

Webview-Specific Listeners import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';

const appWebview = getCurrentWebviewWindow(); appWebview.listen('logged-in', (event) => { localStorage.setItem('session-token', event.payload); });

Managing Listeners 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); });

Listening in Rust Global and Webview Listeners 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")

Unlisten and Listen Once 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()); });

Channels (High-Throughput Streaming)

For better performance than events, use channels:

Rust Channel Setup 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) { 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 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();

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

Execute JavaScript directly from Rust:

Basic Evaluation 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 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

Cargo.toml

[ dependencies ] serialize-to-javascript = "0.1"

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 (emit) 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.

Complete Example: File Watcher Rust Side 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 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('file-changed', (event) => { console.log(File ${event.payload.eventType}: ${event.payload.path}); });

// Cleanup when component unmounts: unlisten();

返回排行榜