事件系统 #

一、事件系统概述 #

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", &notification)
        .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