事件系统 #
一、事件系统概述 #
1.1 什么是事件系统 #
事件系统是 Tauri 中实现松耦合通信的机制。通过事件系统,应用的不同部分可以在不直接依赖的情况下进行通信。
text
┌─────────────────────────────────────────────────────────────┐
│ 事件系统架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 发布者 │ │ 订阅者 │ │
│ │ Publisher │ │ Subscriber │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ emit() listen()│ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 事件总线 │ │
│ │ Event Bus │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 事件类型 #
| 类型 | 说明 | 使用场景 |
|---|---|---|
| 应用事件 | 整个应用级别的事件 | 全局状态变更 |
| 窗口事件 | 特定窗口的事件 | 窗口状态变更 |
| 自定义事件 | 用户定义的事件 | 业务逻辑通信 |
二、后端事件 #
2.1 发送事件 #
rust
use tauri::Emitter;
#[tauri::command]
async fn send_notification(app: tauri::AppHandle, message: String) -> Result<(), String> {
// 发送事件到所有窗口
app.emit("notification", &message)
.map_err(|e| e.to_string())?;
Ok(())
}
2.2 发送到特定窗口 #
rust
use tauri::Emitter;
#[tauri::command]
async fn send_to_window(
app: tauri::AppHandle,
window_label: String,
data: String,
) -> Result<(), String> {
// 发送事件到指定窗口
app.emit_to(&window_label, "custom-event", &data)
.map_err(|e| e.to_string())?;
Ok(())
}
2.3 发送到特定目标 #
rust
use tauri::Emitter;
#[tauri::command]
async fn broadcast_event(app: tauri::AppHandle) -> Result<(), String> {
// 广播到所有监听者
app.emit("global-update", serde_json::json!({
"type": "update",
"timestamp": chrono::Utc::now().to_rfc3339()
})).map_err(|e| e.to_string())?;
Ok(())
}
2.4 后端监听事件 #
rust
use tauri::{Listener, Emitter};
fn setup_event_listeners(app: &tauri::AppHandle) {
// 监听前端发送的事件
app.listen("frontend-event", |event| {
println!("Received from frontend: {:?}", event.payload());
});
}
// 在 setup 中注册
tauri::Builder::default()
.setup(|app| {
setup_event_listeners(app.handle());
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
三、前端事件 #
3.1 监听事件 #
typescript
import { listen, UnlistenFn } from '@tauri-apps/api/event';
// 监听事件
const unlisten: UnlistenFn = await listen<string>('notification', (event) => {
console.log('Notification:', event.payload);
});
// 取消监听
unlisten();
3.2 一次性监听 #
typescript
import { once } from '@tauri-apps/api/event';
// 只监听一次
await once<string>('one-time-event', (event) => {
console.log('This will only fire once:', event.payload);
});
3.3 发送事件 #
typescript
import { emit } from '@tauri-apps/api/event';
// 发送事件
await emit('user-action', {
action: 'click',
target: 'button-submit',
timestamp: Date.now()
});
3.4 发送到特定目标 #
typescript
import { emitTo } from '@tauri-apps/api/event';
// 发送到特定窗口
await emitTo('settings-window', 'config-updated', {
theme: 'dark',
language: 'en'
});
四、事件数据 #
4.1 基本数据类型 #
rust
// 字符串
app.emit("string-event", "Hello World")?;
// 数字
app.emit("number-event", 42)?;
// 布尔
app.emit("bool-event", true)?;
4.2 结构体数据 #
rust
#[derive(Clone, serde::Serialize)]
struct Notification {
title: String,
message: String,
timestamp: i64,
}
#[tauri::command]
async fn send_structured_event(app: tauri::AppHandle) -> Result<(), String> {
let notification = Notification {
title: "Update Available".to_string(),
message: "A new version is ready to install.".to_string(),
timestamp: chrono::Utc::now().timestamp(),
};
app.emit("structured-notification", ¬ification)
.map_err(|e| e.to_string())
}
4.3 前端类型定义 #
typescript
interface Notification {
title: string;
message: string;
timestamp: number;
}
const unlisten = await listen<Notification>('structured-notification', (event) => {
const { title, message, timestamp } = event.payload;
console.log(`${title}: ${message} at ${new Date(timestamp)}`);
});
五、系统事件 #
5.1 窗口事件 #
typescript
import { getCurrentWindow } from '@tauri-apps/api/window';
const window = getCurrentWindow();
// 窗口关闭请求
await window.onCloseRequested(async (event) => {
const confirmed = await confirm('确定要关闭吗?');
if (!confirmed) {
event.preventDefault();
}
});
// 窗口获得焦点
await window.onFocusChanged(({ payload: focused }) => {
console.log('Window focus:', focused);
});
// 窗口大小改变
await window.onResized(({ payload: size }) => {
console.log('Window size:', size.width, size.height);
});
5.2 应用事件 #
rust
tauri::Builder::default()
.setup(|app| {
// 应用启动完成
app.listen(tauri::Event::Ready, |_event| {
println!("Application is ready");
});
// 应用退出
app.listen(tauri::Event::Exit, |_event| {
println!("Application is exiting");
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
5.3 文件拖放事件 #
typescript
import { getCurrentWindow } from '@tauri-apps/api/window';
const window = getCurrentWindow();
// 文件拖放
await window.onFileDropEvent((event) => {
if (event.payload.type === 'drop') {
console.log('Dropped files:', event.payload.paths);
} else if (event.payload.type === 'hover') {
console.log('Files hovering');
} else if (event.payload.type === 'cancel') {
console.log('Drag cancelled');
}
});
六、跨窗口通信 #
6.1 窗口间消息传递 #
rust
use tauri::Emitter;
#[tauri::command]
async fn send_to_main(app: tauri::AppHandle, message: String) -> Result<(), String> {
// 从任意窗口发送到主窗口
app.emit_to("main", "message-from-child", &message)
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn broadcast_to_all(app: tauri::AppHandle, data: serde_json::Value) -> Result<(), String> {
// 广播到所有窗口
app.emit("broadcast", &data)
.map_err(|e| e.to_string())
}
6.2 前端跨窗口通信 #
typescript
// 主窗口监听
import { listen } from '@tauri-apps/api/event';
await listen<string>('message-from-child', (event) => {
console.log('Message from child window:', event.payload);
});
// 子窗口发送
import { emitTo } from '@tauri-apps/api/event';
async function sendMessageToMain(message: string) {
await emitTo('main', 'message-from-child', message);
}
6.3 窗口状态同步 #
rust
use tauri::Emitter;
#[tauri::command]
async fn sync_state(app: tauri::AppHandle, key: String, value: serde_json::Value) {
// 同步状态到所有窗口
app.emit("state-sync", serde_json::json!({
"key": key,
"value": value,
"timestamp": chrono::Utc::now().to_rfc3339()
})).ok();
}
typescript
// 所有窗口监听状态同步
await listen<StateUpdate>('state-sync', (event) => {
const { key, value } = event.payload;
updateLocalState(key, value);
});
七、事件过滤 #
7.1 条件监听 #
typescript
import { listen } from '@tauri-apps/api/event';
await listen<Notification>('notification', (event) => {
// 只处理特定类型的通知
if (event.payload.type === 'important') {
showImportantNotification(event.payload);
}
});
7.2 事件中间件 #
typescript
class EventBus {
private listeners: Map<string, Set<(data: any) => void>> = new Map();
on(event: string, callback: (data: any) => void) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
}
off(event: string, callback: (data: any) => void) {
this.listeners.get(event)?.delete(callback);
}
emit(event: string, data: any) {
this.listeners.get(event)?.forEach(callback => callback(data));
}
}
const eventBus = new EventBus();
八、事件队列 #
8.1 事件缓冲 #
rust
use std::collections::VecDeque;
use std::sync::Mutex;
struct EventQueue {
events: Mutex<VecDeque<serde_json::Value>>,
}
impl EventQueue {
fn new() -> Self {
Self {
events: Mutex::new(VecDeque::new()),
}
}
fn push(&self, event: serde_json::Value) {
let mut events = self.events.lock().unwrap();
events.push_back(event);
}
fn pop(&self) -> Option<serde_json::Value> {
let mut events = self.events.lock().unwrap();
events.pop_front()
}
}
8.2 延迟处理 #
rust
use tauri::Emitter;
#[tauri::command]
async fn queue_event(
app: tauri::AppHandle,
queue: tauri::State<'_, EventQueue>,
event: serde_json::Value,
) {
queue.push(event.clone());
// 延迟发送
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
app.emit("delayed-event", &event).ok();
});
}
九、React 集成 #
9.1 事件 Hook #
typescript
import { useEffect, useState } from 'react';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
export function useEvent<T>(eventName: string) {
const [data, setData] = useState<T | null>(null);
useEffect(() => {
let unlisten: UnlistenFn;
listen<T>(eventName, (event) => {
setData(event.payload);
}).then((fn) => {
unlisten = fn;
});
return () => {
if (unlisten) {
unlisten();
}
};
}, [eventName]);
return data;
}
9.2 使用示例 #
typescript
interface Notification {
title: string;
message: string;
}
function NotificationDisplay() {
const notification = useEvent<Notification>('notification');
if (!notification) return null;
return (
<div className="notification">
<h4>{notification.title}</h4>
<p>{notification.message}</p>
</div>
);
}
十、最佳实践 #
10.1 命名规范 #
typescript
// 推荐:使用命名空间
'user:login'
'user:logout'
'file:open'
'file:save'
'window:focus'
'window:blur'
// 不推荐:模糊命名
'event1'
'data'
'update'
10.2 类型安全 #
typescript
// 定义事件类型映射
interface EventMap {
'user:login': { userId: string; token: string };
'user:logout': void;
'file:open': { path: string; content: string };
'notification': { title: string; message: string };
}
// 类型安全的监听函数
function on<K extends keyof EventMap>(
event: K,
callback: (payload: EventMap[K]) => void
) {
return listen<EventMap[K]>(event, (e) => callback(e.payload));
}
10.3 清理监听器 #
typescript
import { useEffect } from 'react';
import { listen } from '@tauri-apps/api/event';
function Component() {
useEffect(() => {
const unlistenPromises: Promise<UnlistenFn>[] = [];
unlistenPromises.push(
listen('event1', handler1)
);
unlistenPromises.push(
listen('event2', handler2)
);
return () => {
unlistenPromises.forEach(async (p) => {
(await p)();
});
};
}, []);
}
十一、调试技巧 #
11.1 事件日志 #
typescript
// 开发环境启用事件日志
if (import.meta.env.DEV) {
const originalEmit = window.__TAURI__?.event?.emit;
if (originalEmit) {
window.__TAURI__.event.emit = async (event: string, data: any) => {
console.log(`[Event] ${event}:`, data);
return originalEmit(event, data);
};
}
}
11.2 事件监控 #
typescript
// 监控所有事件
const eventLog: Array<{ event: string; time: number; data: any }> = [];
function logEvent(event: string, data: any) {
eventLog.push({
event,
time: Date.now(),
data,
});
console.table(eventLog);
}
十二、总结 #
12.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 事件发送 | 使用 emit() 和 emitTo() |
| 事件监听 | 使用 listen() 和 once() |
| 跨窗口通信 | 通过事件总线实现 |
| 类型安全 | 定义事件类型映射 |
| 清理监听 | 组件卸载时取消监听 |
12.2 下一步 #
现在你已经掌握了 Tauri 的事件系统,接下来让我们学习 窗口创建,了解如何创建和管理应用窗口!
最后更新:2026-03-28