invoke 解决"前端要结果",本节解决另一半:芯侧世界变了,界面如何第一时间知道。用"多窗口同步笔记列表"这个真实需求,把事件监听、退订时机、定向投递三个实战点装到位。读完本节,你的应用在第二个窗口打开时也不会出现"两边列表对不上"的尴尬。
随身笔记长出第二个窗口(比如独立编辑窗)后,第一个问题立刻出现:编辑窗保存了新笔记,主窗的列表还停在旧数据。用轮询当然"能"解决——每秒查一次——但那是把带宽与 CPU 烧在"什么都没发生"上。正解是 2.2 节定下的语义:芯是数据事实源,事实变了就广播事件,关心者自行刷新。
芯侧:命令改完数据顺手广播。
use tauri::{AppHandle, Emitter}; use serde::Serialize; #[derive(Serialize, Clone)] pub struct NoteChanged { pub note_id: u64, pub kind: String } #[tauri::command] fn update_note(app: AppHandle, id: u64, body: String) -> Result<(), String> { store_update(id, &body)?; // 数据变更事实广播给所有窗口 let _ = app.emit("note-changed", NoteChanged { note_id: id, kind: "update".into() }); Ok(()) }
前端侧:监听、刷新、退订三步走。
import { listen, type UnlistenFn } from '@tauri-apps/api/event'; import { listNotes } from './lib/bridge'; let unlisten: UnlistenFn | null = null; onMounted(async () => { notes.value = await listNotes(); unlisten = await listen<{ noteId: number; kind: string }>('noteChanged', async () => { notes.value = await listNotes(); // 简单可靠:收到通知后重拉列表 }); }); onUnmounted(() => { unlisten?.(); }); // 忘了这行 = 监听器泄漏
设计取舍值得说明:事件 payload 只带"变了什么"的最小事实(noteId 与 kind),前端收到后重拉列表而不是重建整个数据副本。原因有二:一是事件可能被错过(窗口未创建完成时),重拉让错过变成无害;二是列表排序过滤逻辑只有一处,事件里塞全量数据迟早出现两份不一致的"事实源"。
前端事件监听挂在 WebView 的 JS 上下文里,窗口不关、监听不散。单页应用里组件反复挂载卸载,每次 mount 都 listen 一次而不 unlisten,监听器指数堆积——症状是"刷新一次列表请求翻倍"。三条军规:
React 版的规范写法(利用 useEffect 清理语义):
useEffect(() => { let alive = true; const p = listen('noteChanged', () => { listNotes().then(n => alive && setNotes(n)); }); return () => { alive = false; p.then(fn => fn()); }; }, []);
广播之外还有"只说给某个窗口"的场景:编辑窗保存成功后,只刷新主窗、不惊动自己。两个工具——emit_to 按标签投递,前端 getCurrentWebviewWindow().emit 从窗口侧发出:
// 芯侧定向:只发给标签为 main 的窗口 let _ = app.emit_to("main", "note-changed", payload);
// 前端定向:只发给编辑窗 import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; await getCurrentWebviewWindow().emitTo('editor', 'open-note', { id: 42 });
标签从哪来?窗口的 label——配置里写的 "main",或创建第二窗口时指定的标签(4.4 节)。定向投递与广播的选用一句话:默认广播,性能或语义有明确理由才定向——定向用多了,"哪个窗口该知道什么"的隐性规则会散落各处难维护。
事件只解决"通知",状态本身放哪要有全局观。三分法:数据事实放芯(笔记正文、设置项——芯有文件系统,天然持久);界面瞬时状态放前端(输入框草稿、展开折叠——与壳无关);跨窗口共享的界面偏好(窗口尺寸、最近路径)交给官方 window-state、store 类插件(第 7 章)。反模式是前端维护全量数据副本再用事件做双写同步——两份事实源必生分歧。记住:事件传事实,不传状态。
壳内两条传送带都通了。下一节处理"外观与形态":无边框窗口、自绘标题栏与多窗口。