electron中定义ipc的完美方案
前语
发现在主进程和渲染进程通信的设计中,很多代码都是重复的,导致最后非常臃肿,且不利于后期扩展
electron项目中 核心文件结构如下
| -- index.js
| -- index.html
| -- ipc
| -- handlers
| -- other.js
| -- xxx.js
| -- index.js
| -- preload.js
方案一
ipc/handlers 里放你需要曝漏给渲染进程和主进程的方法,比如other.js
import processa from "node:process"
export const getEnv = ()=>{
return JSON.stringify(processa.env)
}
// export const setXXX = ()=>{
// xxxx
// return xxxx
// }
ipc/index.js里做将handler 内容分别注册到渲染进程和主进程的逻辑
// 此文件不需要动
import path from "node:path";
import { readdirSync } from "node:fs";
import { ipcMain } from "electron";
export const getIcpMainHandler = async () => {
let allHandler = {};
const dirs = readdirSync(path.join(__dirname, "handlers"), "utf8");
for (const file of dirs) {
const filePath = path.join(__dirname, "handlers", file);
const handlers = await import(filePath);
allHandler = {
...allHandler,
...handlers,
};
}
return allHandler;
};
// 主进程中定义(注册) ipcMain.handle方法
export const registerHandlerForIcpMain = async () => {
const ipcMainHandlers = await getIcpMainHandler();
for (const key in ipcMainHandlers) {
const handler = ipcMainHandlers[key];
ipcMain.handle(key, (event, ...params) => handler(...params));
}
};
// 渲染进程定义(注册) ipcMain.handle方法
export const registerHandlerForPreload = async () => {
const ipcMainHandlers = await getIcpMainHandler();
return Object.keys(ipcMainHandlers);
};
// 以上两个方法的汇总
export const registerHandlerForMainAndPreload = async () => {
registerHandlerForIcpMain();
ipcMain.handle("getIcpHandler", registerHandlerForPreload);
};
在主进程的入口处 index.js,调用此方法
...
import {registerHandlerForMainAndPreload } from "./ipc/index.js";
...
app.whenReady().then(() => {
registerHandlerForMainAndPreload();
...
});
在 preload.js , 也需要做一些处理,用来配合将handler注册渲染进程相关逻辑
const { contextBridge, ipcRenderer } = require("electron");
const main = async () => {
if (!window.electronApi) {
// 获取主进程里的ipcMainHandlers
const ipcMainHandlers = await ipcRenderer.invoke("getIcpHandler");
// 注入到渲染进程的electronApi对象中
const electronAPIContent = {};
for (const handlerName of ipcMainHandlers) {
electronAPIContent[handlerName] = function(){return ipcRenderer.invoke(handlerName, ...arguments)}
}
contextBridge.exposeInMainWorld("electronApi", electronAPIContent);
}
};
main();
最后,在渲染进程的页面中,我们就可以用此handler中的api了
window.electronApi.getEnv()
方案二(推荐,这是方案一的优化)
在方案一种我们可以看到 preload.js 顶层是异步,需要包裹一个main方法。
如果不想这么做,可以利用三方包和sendSync结合来实现
ipc>index.js
// 此文件不需要动
import path from "node:path";
import { readdirSync } from "node:fs";
import { ipcMain } from "electron";
import importSync from "import-sync";
export const getIcpMainHandler = () => {
let allHandler = {};
const dirs = readdirSync(path.join(__dirname, "handlers"), "utf8");
for (const file of dirs) {
const filePath = path.join(__dirname, "handlers", file);
const handlers = importSync(filePath);
allHandler = {
...allHandler,
...handlers,
};
}
return allHandler;
};
// 主进程中定义(注册) ipcMain.handle方法
export const registerHandlerForIcpMain = () => {
const ipcMainHandlers = getIcpMainHandler();
for (const key in ipcMainHandlers) {
const handler = ipcMainHandlers[key];
ipcMain.handle(key, (event, ...params) => handler(...params));
}
};
// 渲染进程定义(注册) ipcMain.handle方法
export const registerHandlerForPreload = () => {
const ipcMainHandlers = getIcpMainHandler();
return Object.keys(ipcMainHandlers);
};
// 以上两个方法的汇总
export const registerHandlerForMainAndPreload = () => {
registerHandlerForIcpMain();
ipcMain.on('getIcpHandler', (event, data) => {
event.returnValue = registerHandlerForPreload();
})
};
preload.js
const { contextBridge, ipcRenderer } = require("electron");
if (!window.electronApi) {
// 获取主进程里的ipcMainHandlers
const ipcMainHandlers = ipcRenderer.sendSync("getIcpHandler");
// 注入到渲染进程的electronApi对象中
const electronAPIContent = {
onUsbChange: (callback) =>
ipcRenderer.on("usbChange", (_event, value) => callback(value)),
};
for (const handlerName of ipcMainHandlers) {
electronAPIContent[handlerName] = function () {
return ipcRenderer.invoke(handlerName, ...arguments);
};
}
contextBridge.exposeInMainWorld("electronApi", electronAPIContent);
}
好处
后续直接在handler文件夹中直接定义方法就行,会自动注入到渲染进程中。
既保证了安全,写起来又方便。