跳转到内容
Tauri

从 Rust 调用前端

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

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

事件系统

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

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

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

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

AppHandleWebviewWindow 类型实现了事件系统特征 ListenerEmitter

事件要么是全局的(传递给所有监听器),要么是特定于 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 事件

要向特定 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', () => {});

监听 Rust 上的事件

全局事件和特定于 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,
});

执行 JavaScript

要在 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 包。


© 2025 Tauri Contributors. CC-BY / MIT