2.1 节认了层,本节跟着一次 invoke 走完全程。这条旅程是第 4 章(前端调用)、第 5 章(写命令)、第 6 章(权限检查)三章共用的时序基准——以后任何"调用没生效""参数没对上"的排错,都从这条传送带上找断点。
传送带能转,先要有运单登记。Rust 侧两步:给函数贴 #[tauri::command] 标记,再把函数名交给启动时的路由宏:
#[tauri::command] fn add_note(title: String, body: String) -> Result<Note, String> { let note = Note { id: next_id(), title, body, created_at: now() }; save_to_disk(¬e)?; Ok(note) } pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![add_note, greet]) .run(tauri::generate_context!()) .expect("failed to run"); }
generate_handler! 在编译期生成一张"命令名到函数"的路由表。前端调用的字符串名字必须精确匹配函数名的蛇形转驼峰形式:add_note 在前端叫 addNote。漏登记或名字拼错,前端会收到"未找到命令"类错误——这是新手第一高频坑。
前端按下保存按钮,await invoke('addNote', { title, body }) 执行,接下来依次发生:
八站里值得停下来的有四处。第二站,序列化:参数对象与返回值都是 JSON,Rust 侧的参数类型必须能从 JSON 反序列化(实现 Deserialize),返回值必须能序列化(实现 Serialize)——第 5 章的 serde 细节全在服务这一站。第三站,权限闸门:桥接层核对当前窗口是否被 Capabilities 授权调用此命令,未授权直接拒绝,函数体根本不会执行——这就是"默认拒绝"在运行时的样子。第六站,命名转换:Rust 蛇形参数 created_at 在前端对象里写作 created_at 的驼峰 createdAt,转换由宏自动完成;想关掉转换也可以显式指定 rename_all,但两端约定要一致。第八站,错误回传:Err 分支同样走序列化,前端 await 拿到的是 rejected promise——错误处理的完整方案在第 5 章第 4 节。
命令是"前端问、芯答",事件是"双方随时喊话"。芯可以向前端广播进度、前端也能向芯发事件。什么时候用事件而不是命令?给一个可操作的判断:语义上需要"推送"或"多方接收"的用事件,需要"请求响应"的用命令。 反解析进度条就是典型——芯在后台解析,完成度不由前端请求产生,用事件推:
use tauri::Emitter; #[tauri::command] fn parse_big_file(app: tauri::AppHandle, path: String) -> Result<(), String> { for (i, chunk) in read_chunks(&path).enumerate() { process(chunk); if i % 50 == 0 { // 向所有窗口广播解析进度 let _ = app.emit("parse-progress", i as f32 / total(&path)); } } Ok(()) }
import { listen } from '@tauri-apps/api/event'; const unlisten = await listen('parse-progress', (event) => { bar.style.width = `${Math.round(event.payload * 100)}%`; }); // 组件卸载时取消订阅,否则监听器泄漏 onUnmounted(() => unlisten());
两个工程细节常被忽略:其一,listen 返回取消函数,组件卸载必须调用,否则窗口关了监听器还挂着;其二,事件广播没有回执,不能拿事件做需要确认的事——"前端通知芯开始解析"应该走命令,"芯汇报进度"才走事件。把两条通道的分工混起来,是状态同步类 bug 的头号来源。
| 需求形态 | 选用 | 理由 |
|---|---|---|
| 前端要拿到结果才能继续 | 命令 | 有响应、有错误回传 |
| 芯主动汇报进度或状态变化 | 事件 | 推送语义,无需请求方在场 |
| 多窗口同时感知同一变化 | 事件广播 | 一次发送各方可达 |
| 需要确保执行且可失败重试 | 命令 | 错误能被 await 捕获处理 |
| 单窗口定向通知 | 事件定向 | 指定标签投递,避免打扰他窗 |
传送带的机械结构清楚了。下一节端详壳体本身——三块系统 WebView 差在哪、怎么适配、怎么分别调试。