窗口通信 #
一、通信概述 #
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