Tauri 如何与前端协作
本教程共 48 篇 · 第 14 篇 · 更新于 2026-08-09 · 约 8 分钟阅读
本节目标:学完能说清 Tauri 应用里前端页面和 Rust 后端分别是怎么跑的,两者之间靠哪两条通道通信,以及前端那句
invoke背后是什么。
很多新手第一次用 Tauri,会有个困惑:我的界面是网页写的,可它又能调用系统文件对话框、能读写本地磁盘——这网页明明跑在浏览器里是做不到这些的。答案就藏在「前端跑在哪」和「前后端怎么通信」这两件事里。本章把协作机制一次讲透,不写完整项目,只讲清楚原理和最小调用方式。
前端其实跑在 WebView 里
Tauri 的前端并不是跑在 Chrome、Edge 这种独立浏览器里,而是跑在操作系统自带的 WebView 控件中。Windows 上叫 WebView2(基于 Chromium),macOS 上叫 WKWebView(基于 Safari 内核),Linux 上叫 WebKitGTK。你可以把它理解成一个「没有地址栏、没有工具栏的迷你浏览器窗口」,它只负责把你的 HTML/CSS/JS 渲染出来。
因为 WebView 就是个网页渲染引擎,所以你熟悉的任何前端技术——原生 JS、Vue、React、Svelte——都能直接用。但网页本身是被「关在沙箱里」的,它不能直接碰操作系统。那它怎么打开文件、怎么读写数据?这就需要一个「中间人」把网页的请求转交给真正有权限的 Rust 后端。这个中间人,就是 Tauri 的进程模型和通信通道。
Note正因为界面是网页,而能力来自 Rust,所以 Tauri 应用天然分成「前端(WebView 里的 JS)」和「后端(Rust 主进程)」两个部分。它们是两个独立的运行环境,不能直接共享变量,只能「说话」。
唯一通道:进程间通信(IPC)
前端(JavaScript)和后端(Rust)不在同一个进程,它们之间的所有交流,都通过一套叫 IPC(Inter-Process Communication,进程间通信)的机制完成。在 Tauri 2.x 中,IPC 给你两条通道,分工明确:
- 命令(command):前端「主动呼叫」后端的一个具体函数,可以传参数、拿返回值,像调一个远程函数。它类型是安全的(参数和返回值都有明确类型)。
- 事件(event):后端「主动广播」一条消息给前端(或反过来),适合通知、进度更新这类场景。它更像发订阅,不保证类型安全、不能拿返回值。
记住一句话:命令是「前端问、后端答」,事件是「谁都可以喊一嗓子、谁在听谁收」。下面分别看。
命令:类型安全的远程调用
命令是前后端协作的主力。后端那边,你只要写一个普通的 Rust 函数,再给它贴一个 #[tauri::command] 标签,它就变成了一个可以被前端调用的命令。
// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: String) -> String {
format!("你好,{}!这条消息来自 Rust。", name)
}
光写函数还不够,你得告诉 Tauri「有这么个命令可以调用」,在 run() 函数里用 generate_handler! 注册它:
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
前端这边,用 invoke 函数去「点名」调用它。命令名要和 Rust 那边完全一致(这里是 greet),参数用对象传,返回的是一个 Promise:
// 前端任意 .ts 文件
import { invoke } from '@tauri-apps/api/core';
async function sayHi() {
const msg = await invoke('greet', { name: '小明' });
console.log(msg); // 输出:你好,小明!这条消息来自 Rust。
}
这里有个细节:Rust 函数的参数名是 name(snake_case),但从前端传参要用 name(Tauri 默认把参数映射成 camelCase 也认,简单类型同名即可)。如果 Rust 端写了 #[tauri::command(rename_all = "snake_case")],那前端就必须用 snake_case 的键名。命令还支持 async、支持返回 Result 来表示成功或失败、支持把文件等二进制用 tauri::ipc::Response 高效返回——这些是进阶用法,知道「命令能传参、能返回值、能报错」就够了。
Warning在 Tauri 2.x 的安全模型下,命令默认需要被「能力(capability)」授权才能从前端调用。脚手架生成的项目已经在
src-tauri/capabilities/里配好了默认权限;如果你新增了一个自定义命令却忘了授权,前端调用时会收到权限错误。把命令加进能力文件即可,例如:
{
"identifier": "default",
"windows": ["main"],
"permissions": ["core:default", "greet"]
}
事件:谁都可以广播的消息
命令适合「一问一答」,但有些场景是后端主动、持续地往前端的。比如后端在后台压缩文件,想实时把进度「10%、20%……」推给界面。这时命令就不合适了(它是前端发起的),该用事件。
后端用 app.emit 广播,前端用 listen 接收:
// 后端:广播一个进度事件
app.emit("progress", "已完成 50%").unwrap();
// 前端:监听这个事件
import { listen } from '@tauri-apps/api/event';
await listen('progress', (event) => {
console.log(event.payload); // 输出:已完成 50%
});
事件的特点要记牢:它总是异步的、不带返回值、负载(payload)只能是 JSON 能表达的数据、且默认是「群发」给所有监听器。如果你只想发给某个特定窗口,可以用 emit_to 指定窗口标签。事件的代价是少了类型保护——传什么、收什么,得你和后端约定好,编译器帮不了你。所以「要返回值、要类型安全」用命令,「纯通知、流式推送」用事件。
JS API 包:前端的工具箱
你刚才看到的 invoke、listen,都不是浏览器自带的东西,而是来自一个 npm 包:@tauri-apps/api。它是 Tauri 为前端准备的官方 JavaScript API 工具箱,把底层 IPC 的复杂细节封装成了好调用的函数。
安装它很简单,版本以官方最新为准:
npm install @tauri-apps/api@latest
这个包按能力分了模块,最常用的几个:
@tauri-apps/api/core:提供invoke,用来调用命令(最核心)。@tauri-apps/api/event:提供listen、emit,用来收发事件。@tauri-apps/api/window、@tauri-apps/api/webviewWindow:操作窗口。@tauri-apps/api/path、@tauri-apps/api/fs等:访问文件路径、文件系统等原生能力(部分需要对应插件和权限)。
Tip如果你不想引入 npm 包,也可以在
tauri.conf.json里开启app.withGlobalTauri,这样 Tauri 会把 API 挂到全局的window.__TAURI__上,用window.__TAURI__.core.invoke(...)调用。但官方推荐用 npm 包的方式,类型提示更友好、打包更干净。
小结:一张协作图
把前面几节串起来,Tauri 的协作关系就是:你在 WebView 里写的网页,想干任何「超越网页」的活,都通过 @tauri-apps/api 包,要么用 invoke 走「命令」通道去调 Rust 函数,要么用 emit/listen 走「事件」通道收发消息;Rust 后端处理完系统层面的事,再把结果原路返回。理解了这条链路,后面无论接入原生前端还是 Vue、React,本质都只是「前端用什么语法去写那句 invoke」的区别。
接下来两章,我们就分别看两种最典型的前端写法:先是最轻量的原生 HTML/CSS/JS,再是更工程化的 Vue 3。