跳转到内容

从 Rust 调用前端

本文档包含如何从 Rust 代码与应用程序前端通信的指南。 要了解如何从前端与 Rust 代码通信,请参阅 从前端调用 Rust 。

Tauri 应用程序的 Rust 端可以利用 Tauri 事件系统、 使用通道或直接评估 JavaScript 代码来调用前端。

Tauri 提供了一个简单的事件系统,您可以使用它在 Rust 和前端之间进行双向通信。

事件系统是为需要传输少量数据或需要实现多消费者多生产者模式(例如推送通知系统)的情况而设计的。

事件系统并非为低延迟或高吞吐量场景而设计。 请参阅 通道部分 ,了解针对流数据优化的实现。

Tauri 命令和 Tauri 事件之间的主要区别在于,事件没有强类型支持, 事件有效负载始终是 JSON 字符串,这使得它们不适合更大的消息, 并且不支持 功能 系统对事件数据和渠道进行细粒度控制。

AppHandle 和 WebviewWindow 类型实现了事件系统特征 Listener 和 Emitter 。

事件要么是全局的(传递给所有监听器),要么是特定于 webview 的(仅传递给与给定标签匹配的 webview)。

要触发全局事件,您可以使用 Emitter#emit 函数:

src-tauri/src/lib.rs
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 注册的监听器触发事件​​,您可以使用 Emitter#emit_to 函数:

src-tauri/src/lib.rs
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 列表的事件。 以下示例中,我们向主 webview 和文件查看器 webview 发出一个打开文件事件:

src-tauri/src/lib.rs
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();
}

事件负载可以是任何 可序列化 的类型,只要它实现了 Clone 接口。 让我们来进一步改进下载事件的示例,使用一个对象在每个事件中发出更多信息:

src-tauri/src/lib.rs
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();
}

Tauri 提供 API 来监听 webview 和 Rust 中的事件。

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

  • 监听全局事件

    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 函数:

import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('download-started', (event) => {});
unlisten();

此外, Tauri 还提供了一个实用函数,用于精确监听一次事件:

import { once } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
once('ready', (event) => {});
const appWebview = getCurrentWebviewWindow();
appWebview.once('ready', () => {});

全局事件和特定于 webview 的事件也会传递给在 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 函数:

// 在事件处理程序作用域外取消监听:
let event_id = app.listen("download-started", |event| {});
app.unlisten(event_id);
// 当满足某些事件条件时取消监听:
let handle = app.handle().clone();
app.listen("status-changed", |event| {
if event.data == "ready" {
handle.unlisten(event.id);
}
});

此外, Tauri 还提供了一个实用函数,用于精确监听一次事件:

app.once("ready", |event| {
println!("app is ready");
});

在这种情况下,事件监听器在第一次触发后立即取消注册。

事件系统旨在提供简单的双向通信,并可在应用程序中全局使用。 其底层直接执行 JavaScript 代码,因此可能不适合发送大量数据。

通道旨在快速传递有序数据。它们在内部用于流式传输操作, 例如下载进度、子进程输出和 WebSocket 消息。

让我们重写下载命令示例以使用通道而不是事件系统:

src-tauri/src/lib.rs
use tauri::{AppHandle, ipc::Channel};
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", rename_all_fields = "camelCase", tag = "event", content = "data")]
enum DownloadEvent<'a> {
Started {
url: &'a str,
download_id: usize,
content_length: usize,
},
Progress {
download_id: usize,
chunk_length: usize,
},
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();
}

调用下载命令时,您必须创建通道并将其作为参数提供:

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

要在 webview 上下文中直接执行任何 JavaScript 代码,您可以使用 WebviewWindow#eval 函数:

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


© 2026 Tauri Contributors. CC-BY / MIT