---
title: 从 Rust 调用前端
i18nReady: true
---

import { Content as FrontendListen } from './_sections/frontend-listen.mdx';

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

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

## 事件系统

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

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

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

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

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

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

### 全局事件

要触发全局事件，您可以使用 [Emitter#emit] 函数：

```rust title="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();
}
```

:::note
全局事件传递给**所有**监听者
:::

### Webview 事件

要向特定 webview 注册的监听器触发事件​​，您可以使用 [Emitter#emit_to] 函数：

```rust title="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 发出一个打开文件事件：

```rust title="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();
}
```

:::note
Webview 特有的事件**不会**被触发到常规的全局事件监听器中。
要监听**任何**事件，您必须使用 `listen_any` 函数而不是 `listen` 函数，
后者将监听器定义为所有已发出事件的集合。
:::

### 事件负载

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

```rust title="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 中的事件。

#### 监听前端事件

<FrontendListen />

## 通道

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

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

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

```rust title="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();
}
```

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

```ts
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`] 函数：

```rust title="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] 包。

[`WebviewWindow#eval`]: https://docs.rs/tauri/2.0.0/tauri/webview/struct.WebviewWindow.html#method.eval
[serialize-to-javascript]: https://docs.rs/serialize-to-javascript/latest/serialize_to_javascript/
[AppHandle]: https://docs.rs/tauri/2.0.0/tauri/struct.AppHandle.html
[WebviewWindow]: https://docs.rs/tauri/2.0.0/tauri/webview/struct.WebviewWindow.html
[Listener]: https://docs.rs/tauri/2.0.0/tauri/trait.Listener.html
[Emitter]: https://docs.rs/tauri/2.0.0/tauri/trait.Emitter.html
[Emitter#emit]: https://docs.rs/tauri/2.0.0/tauri/trait.Emitter.html#tymethod.emit
[Emitter#emit_to]: https://docs.rs/tauri/2.0.0/tauri/trait.Emitter.html#tymethod.emit_to
[Emitter#emit_filter]: https://docs.rs/tauri/2.0.0/tauri/trait.Emitter.html#tymethod.emit_filter
[Clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html
[Serialize]: https://serde.rs/impl-serialize.html
[从前端调用 Rust]: /develop/calling-rust/
[功能]: /security/capabilities/
