窗口通信 #

一、通信概述 #

1.1 通信架构 #

text
┌─────────────────────────────────────────────────────────────┐
│                      窗口通信架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐                    ┌─────────────┐        │
│  │  窗口 A     │                    │  窗口 B     │        │
│  │             │    emit/emitTo     │             │        │
│  │             │ ─────────────────► │             │        │
│  │             │                    │             │        │
│  │             │ ◄───────────────── │             │        │
│  │             │      listen        │             │        │
│  └─────────────┘                    └─────────────┘        │
│                                                             │
│                         │                                   │
│                         ▼                                   │
│              ┌─────────────────────┐                        │
│              │      后端          │                        │
│              │   (Rust Core)      │                        │
│              └─────────────────────┘                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 通信方式 #

方式 说明 使用场景
事件通信 通过事件系统传递消息 轻量级通知
命令调用 通过 invoke 调用后端 需要后端处理
状态共享 共享状态数据 数据同步
文件传递 通过文件传递数据 大数据量

二、事件通信 #

2.1 发送事件到指定窗口 #

typescript
import { emitTo } from '@tauri-apps/api/event';

// 发送到指定窗口
await emitTo('settings', 'config-updated', {
    theme: 'dark',
    language: 'zh-CN'
});

2.2 接收窗口事件 #

typescript
import { listen } from '@tauri-apps/api/event';

// 监听来自其他窗口的事件
const unlisten = await listen<ConfigUpdate>('config-updated', (event) => {
    console.log('Config updated:', event.payload);
    updateConfig(event.payload);
});

// 取消监听
unlisten();

2.3 广播事件 #

typescript
import { emit } from '@tauri-apps/api/event';

// 广播到所有窗口
await emit('global-notification', {
    type: 'info',
    message: 'Application updated'
});

三、数据传递 #

3.1 基本数据类型 #

typescript
// 发送字符串
await emitTo('detail', 'message', 'Hello World');

// 发送数字
await emitTo('detail', 'count', 42);

// 发送布尔值
await emitTo('detail', 'enabled', true);

3.2 对象数据 #

typescript
interface UserData {
    id: number;
    name: string;
    email: string;
}

// 发送对象
const user: UserData = {
    id: 1,
    name: 'Alice',
    email: 'alice@example.com'
};

await emitTo('profile', 'user-data', user);
typescript
// 接收对象
await listen<UserData>('user-data', (event) => {
    const user = event.payload;
    console.log(`User: ${user.name} (${user.email})`);
});

3.3 大数据传递 #

typescript
// 对于大数据,使用分块传输
interface ChunkData {
    id: string;
    index: number;
    total: number;
    data: string;
}

async function sendLargeData(targetWindow: string, data: string) {
    const chunkSize = 1024 * 100; // 100KB per chunk
    const chunks = Math.ceil(data.length / chunkSize);
    const id = crypto.randomUUID();

    for (let i = 0; i < chunks; i++) {
        const chunk = data.slice(i * chunkSize, (i + 1) * chunkSize);
        
        await emitTo(targetWindow, 'data-chunk', {
            id,
            index: i,
            total: chunks,
            data: chunk
        } as ChunkData);
    }
}
typescript
// 接收分块数据
const chunkBuffers = new Map<string, string[]>();

await listen<ChunkData>('data-chunk', (event) => {
    const { id, index, total, data } = event.payload;
    
    if (!chunkBuffers.has(id)) {
        chunkBuffers.set(id, new Array(total).fill(''));
    }
    
    chunkBuffers.get(id)![index] = data;
    
    // 检查是否接收完成
    const buffer = chunkBuffers.get(id)!;
    if (buffer.every(chunk => chunk !== '')) {
        const completeData = buffer.join('');
        processData(completeData);
        chunkBuffers.delete(id);
    }
});

四、请求-响应模式 #

4.1 实现请求-响应 #

typescript
// RequestResponse.ts
import { emitTo, listen } from '@tauri-apps/api/event';

interface Request {
    id: string;
    action: string;
    data: unknown;
}

interface Response {
    id: string;
    success: boolean;
    data?: unknown;
    error?: string;
}

class RequestResponse {
    private pendingRequests = new Map<string, {
        resolve: (value: unknown) => void;
        reject: (error: Error) => void;
        timeout: ReturnType<typeof setTimeout>;
    }>();

    async request<T>(
        targetWindow: string,
        action: string,
        data: unknown,
        timeout = 5000
    ): Promise<T> {
        const id = crypto.randomUUID();

        return new Promise((resolve, reject) => {
            const timeoutId = setTimeout(() => {
                this.pendingRequests.delete(id);
                reject(new Error('Request timeout'));
            }, timeout);

            this.pendingRequests.set(id, {
                resolve: resolve as (value: unknown) => void,
                reject,
                timeout: timeoutId
            });

            emitTo(targetWindow, 'request', { id, action, data });
        });
    }

    async handleRequest(
        handler: (action: string, data: unknown) => Promise<unknown>
    ) {
        await listen<Request>('request', async (event) => {
            const { id, action, data } = event.payload;
            
            try {
                const result = await handler(action, data);
                await emitTo(event.windowLabel, 'response', {
                    id,
                    success: true,
                    data: result
                });
            } catch (error) {
                await emitTo(event.windowLabel, 'response', {
                    id,
                    success: false,
                    error: String(error)
                });
            }
        });

        await listen<Response>('response', (event) => {
            const { id, success, data, error } = event.payload;
            const pending = this.pendingRequests.get(id);
            
            if (pending) {
                clearTimeout(pending.timeout);
                this.pendingRequests.delete(id);
                
                if (success) {
                    pending.resolve(data);
                } else {
                    pending.reject(new Error(error));
                }
            }
        });
    }
}

export const requestResponse = new RequestResponse();

4.2 使用请求-响应 #

typescript
// 发送请求
const config = await requestResponse.request<Config>(
    'settings',
    'get-config',
    { key: 'theme' }
);

// 处理请求
await requestResponse.handleRequest(async (action, data) => {
    if (action === 'get-config') {
        return getConfig((data as { key: string }).key);
    }
    throw new Error('Unknown action');
});

五、状态共享 #

5.1 共享状态管理 #

typescript
// SharedState.ts
import { emit, listen } from '@tauri-apps/api/event';

class SharedState {
    private state: Map<string, unknown> = new Map();
    private listeners: Map<string, Set<(value: unknown) => void>> = new Map();

    constructor() {
        this.init();
    }

    private async init() {
        await listen<{ key: string; value: unknown }>(
            'state-update',
            (event) => {
                const { key, value } = event.payload;
                this.state.set(key, value);
                this.notifyListeners(key, value);
            }
        );
    }

    get<T>(key: string): T | undefined {
        return this.state.get(key) as T | undefined;
    }

    async set(key: string, value: unknown) {
        this.state.set(key, value);
        await emit('state-update', { key, value });
    }

    subscribe(key: string, callback: (value: unknown) => void) {
        if (!this.listeners.has(key)) {
            this.listeners.set(key, new Set());
        }
        this.listeners.get(key)!.add(callback);

        // 返回取消订阅函数
        return () => {
            this.listeners.get(key)?.delete(callback);
        };
    }

    private notifyListeners(key: string, value: unknown) {
        this.listeners.get(key)?.forEach(callback => callback(value));
    }
}

export const sharedState = new SharedState();

5.2 使用共享状态 #

typescript
// 设置状态
await sharedState.set('theme', 'dark');
await sharedState.set('user', { name: 'Alice' });

// 获取状态
const theme = sharedState.get<string>('theme');
const user = sharedState.get<User>('user');

// 监听状态变化
const unsubscribe = sharedState.subscribe('theme', (value) => {
    console.log('Theme changed:', value);
    applyTheme(value as string);
});

六、后端中转 #

6.1 通过后端转发消息 #

rust
use tauri::Emitter;

#[tauri::command]
async fn forward_message(
    app: tauri::AppHandle,
    target: String,
    event: String,
    data: serde_json::Value,
) -> Result<(), String> {
    app.emit_to(&target, &event, &data)
        .map_err(|e| e.to_string())
}
typescript
// 前端调用
await invoke('forward_message', {
    target: 'settings',
    event: 'config-update',
    data: { theme: 'dark' }
});

6.2 后端广播 #

rust
use tauri::Emitter;

#[tauri::command]
async fn broadcast_update(
    app: tauri::AppHandle,
    data: serde_json::Value,
) -> Result<(), String> {
    app.emit("global-update", &data)
        .map_err(|e| e.to_string())
}

七、通信模式 #

7.1 发布-订阅模式 #

typescript
// PubSub.ts
import { emit, listen } from '@tauri-apps/api/event';

class PubSub {
    private subscriptions = new Map<string, Set<string>>();

    async publish(topic: string, message: unknown) {
        await emit(`topic:${topic}`, message);
    }

    async subscribe(topic: string, callback: (message: unknown) => void) {
        return listen(`topic:${topic}`, (event) => {
            callback(event.payload);
        });
    }
}

export const pubsub = new PubSub();

7.2 使用发布-订阅 #

typescript
// 发布消息
await pubsub.publish('notifications', {
    type: 'info',
    message: 'New update available'
});

// 订阅消息
const unlisten = await pubsub.subscribe('notifications', (message) => {
    showNotification(message);
});

7.3 管道模式 #

typescript
// Pipeline.ts
type Handler = (data: unknown) => unknown | Promise<unknown>;

class Pipeline {
    private handlers: Handler[] = [];

    use(handler: Handler) {
        this.handlers.push(handler);
        return this;
    }

    async process(data: unknown): Promise<unknown> {
        let result = data;
        
        for (const handler of this.handlers) {
            result = await handler(result);
        }
        
        return result;
    }
}

// 使用管道
const pipeline = new Pipeline()
    .use(validateData)
    .use(transformData)
    .use(saveData);

const result = await pipeline.process(inputData);

八、React 集成 #

8.1 窗口通信 Hook #

typescript
import { useEffect, useState } from 'react';
import { listen, UnlistenFn } from '@tauri-apps/api/event';

export function useWindowEvent<T>(event: string, initialValue?: T) {
    const [data, setData] = useState<T | undefined>(initialValue);

    useEffect(() => {
        let unlisten: UnlistenFn;

        listen<T>(event, (e) => {
            setData(e.payload);
        }).then((fn) => {
            unlisten = fn;
        });

        return () => {
            if (unlisten) {
                unlisten();
            }
        };
    }, [event]);

    return data;
}

8.2 使用示例 #

tsx
function UserProfile() {
    const user = useWindowEvent<User>('user-update');

    if (!user) {
        return <div>Loading...</div>;
    }

    return (
        <div>
            <h2>{user.name}</h2>
            <p>{user.email}</p>
        </div>
    );
}

九、调试技巧 #

9.1 通信日志 #

typescript
// 开发环境启用通信日志
if (import.meta.env.DEV) {
    const originalEmitTo = window.__TAURI__?.event?.emitTo;
    if (originalEmitTo) {
        window.__TAURI__.event.emitTo = async (target: string, event: string, data: any) => {
            console.log(`[IPC] ${target} <- ${event}:`, data);
            return originalEmitTo(target, event, data);
        };
    }
}

9.2 消息追踪 #

typescript
class MessageTracker {
    private messages: Array<{
        time: number;
        from: string;
        to: string;
        event: string;
        data: unknown;
    }> = [];

    track(from: string, to: string, event: string, data: unknown) {
        this.messages.push({
            time: Date.now(),
            from,
            to,
            event,
            data
        });
    }

    getHistory() {
        return this.messages;
    }

    printHistory() {
        console.table(this.messages);
    }
}

export const messageTracker = new MessageTracker();

十、最佳实践 #

10.1 类型安全 #

typescript
// 定义事件类型
interface EventMap {
    'user:login': { userId: string };
    'user:logout': void;
    'config:update': { key: string; value: unknown };
}

// 类型安全的发送函数
async function sendEvent<K extends keyof EventMap>(
    target: string,
    event: K,
    data: EventMap[K]
) {
    await emitTo(target, event, data);
}

// 类型安全的监听函数
async function onEvent<K extends keyof EventMap>(
    event: K,
    callback: (data: EventMap[K]) => void
) {
    return listen<EventMap[K]>(event, (e) => callback(e.payload));
}

10.2 错误处理 #

typescript
async function safeEmit(target: string, event: string, data: unknown) {
    try {
        await emitTo(target, event, data);
    } catch (error) {
        console.error(`Failed to send event to ${target}:`, error);
        // 可以选择重试或记录日志
    }
}

十一、总结 #

11.1 核心要点 #

要点 说明
事件通信 使用 emit/emitTo 发送事件
数据传递 支持各种数据类型
请求响应 实现双向通信模式
状态共享 跨窗口状态同步
后端中转 通过后端转发消息

11.2 下一步 #

现在你已经掌握了窗口通信,接下来让我们学习 React集成,了解如何在 Tauri 中使用 React 框架!

最后更新:2026-03-28