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文件夹中直接定义方法就行,会自动注入到渲染进程中。
既保证了安全,写起来又方便。

posted @ 2024-09-23 18:17  丁少华  阅读(41)  评论(0编辑  收藏  举报