背景
随着项目越来越大,各种 IPC 之间的数据传输越来越混乱,因此急需 typescript 的支持,以达到完整的参数和返回值的提示,减少来回查阅的成本
直接上代码
类型文件定义
首先我们定义一个 types.ts
文件,用来存放 IPC 中每个消息的类型
// 由于我们要实现 主进程 和 渲染进程 的双向通讯,
// 因此定义了俩 type
// 从 渲染进程 传过来的消息类型
export type RenderMessage = {
getUserNameById(userID: number): Promise<string>;
};
// 从 主进程 发送到渲染进程的消息类型
export type MainMessage = {
newUserJoin(userID: number): void;
};
主进程 IPC 实现
来看看主进程中的 IPC
类实现
import { ipcMain, BrowserWindow } from "electron";
type MessageObj<T> = {
[K in keyof T]: (...args: any) => void;
};
export class IPCMain<
MessageType extends MessageObj<MessageType>,
BackgroundMessageType extends MessageObj<BackgroundMessageType>
> {
channel: string;
listeners: Partial<Record<keyof MessageType, any>> = {};
constructor(channel: string = "IPC-bridge") {
this.channel = channel;
this._bindMessage();
}
on<T extends keyof MessageType>(
name: T,
fn: (...args: Parameters<MessageType[T]>) => ReturnType<MessageType[T]>
): void {
if (this.listeners[name])
throw new Error(`消息处理器 ${String(name)} 已存在`);
this.listeners[name] = fn;
}
off<T extends keyof MessageType>(action: T): void {
if (this.listeners[action]) {
delete this.listeners[action];
}
}
async send<T extends keyof BackgroundMessageType>(
name: T,
...payload: Parameters<BackgroundMessageType[T]>
): Promise<void> {
// 获取所有打开的窗口
const windows = BrowserWindow.getAllWindows();
// 向每个窗口发送消息
windows.forEach((window) => {
// @ts-ignore
window.webContents.send(this.channel, {
name: name,
payload: payload,
});
});
}
_bindMessage() {
ipcMain.handle(this.channel, this._handleReceivingMessage.bind(this));
}
async _handleReceivingMessage(
_: any,
payload: { name: keyof MessageType; payload: any }
) {
try {
if (this.listeners[payload.name]) {
const res = await this.listeners[payload.name](...payload.payload);
return {
type: "success",
result: res,
};
} else {
throw new Error(`未知的 IPC 消息 ${String(payload.name)}`);
}
} catch (e: any) {
return {
type: "error",
error: e.toString(),
};
}
}
}
渲染进程 IPC 实现
我们在渲染进程里面建立以下这个类
import { ipcRenderer, on } from "#preload";
import { ErrorMessage } from "@/utils/message";
type MessageObj<T> = {
[K in keyof T]: (...args: any) => void;
};
export class IPCRenderer<
MessageType extends MessageObj<MessageType>,
BackgroundMessageType extends MessageObj<BackgroundMessageType>
> {
channel: string;
listeners: Partial<Record<keyof BackgroundMessageType, any>> = {};
constructor(channel: string = "IPC-bridge") {
this.channel = channel;
this._bindMessage();
}
send<T extends keyof MessageType>(
name: T,
...payload: Parameters<MessageType[T]>
): Promise<Awaited<ReturnType<MessageType[T]>>> {
return new Promise(async (res, rej) => {
const data = await ipcRenderer.invoke(this.channel, {
name: String(name),
payload,
});
if (data.type === "success") {
return res(data.result);
} else {
// 主进程如果返回错误的话,在这里显示到 UI 上
ErrorMessage(data.error);
return rej(data.error);
}
});
}
on<T extends keyof BackgroundMessageType>(
name: T,
fn: (...args: Parameters<BackgroundMessageType[T]>) => void
): () => void {
this.listeners[name] = this.listeners[name] || [];
this.listeners[name].push(fn);
// 提供删除方法,方便 react 中的 useEffect 使用
return () => {
if (this.listeners[name].includes(fn)) {
const index = this.listeners[name].indexOf(fn);
this.listeners[name].splice(index, 1);
}
};
}
_handleReceivingMessage(
_,
payloadData: { name: keyof BackgroundMessageType; payload: any }
) {
const { name, payload } = payloadData;
if (this.listeners[name]) {
for (let fn of this.listeners[String(name)]) {
fn(...payload);
}
}
}
_bindMessage() {
on(this.channel, this._handleReceivingMessage.bind(this));
}
}
使用
主进程
// 首先 new 一下
const IPC = new IPCMain<RenderMessage, MainMessage>();
// 绑定处理函数
// 完全的 typescript 检测!
IPC.on("getUserNameById", (userID: string) => {
return Promise.resolve("张三");
});
// 主进程向渲染进程发消息
// 完全的 typescript 检测!
IPC.send("newUserJoin", 9);
渲染进程
const IPC = new IPCRenderer<RenderMessage, MainMessage>();
function App() {
useEffect(() => {
// 完全的 typescript 检测
const remove = IPC.on("newUserJoin", (id) => {
console.log(id);
});
return () => {
remove();
};
}, []);
function handleGetName() {
// 完全的 typescript 检测!
IPC.send("getUserNameById", 9).then((userName) => {
console.log(userName);
});
}
return <button onClick={handleGetName}>获取名称</button>;
}