electron的渲染进程与主进程之间的通讯

688 阅读5分钟

渲染进程与主进程通讯的三种模式

在 Electron 中,有两种类型的进程:主进程和渲染进程。主进程负责创建浏览器窗口,而渲染进程则在每个窗口中运行网页。由于安全原因,这两种进程不能直接通信。为了解决这个问题,Electron 提供了 ipcMain 和 ipcRenderer 模块来实现进程间通讯(IPC)。

〇. 通讯前准备

1.创建electron项目

在项目中,html文件为渲染进程,main.js为主进程,而预加载脚本如同通道,链接进程

2.准备页面——html文件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>进程间通讯</title>
  </head>
  <body></body>
  <script src="./renderer.js"></script>
</html>

3.准备主进程文件——main.js

// 引入所需的模块
const { app, BrowserWindow, Menu, ipcMain, dialog } = require("electron/main");
const path = require("node:path");


// 定义函数,用于创建窗口
function 创建窗口() {
  const 窗口 = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true, // 启用 Node.js 集成,允许在渲染进程中使用 Node.js 模块
      contextIsolation: true, // 启用上下文隔离,增加安全性,防止渲染进程直接访问主进程的 API
      javascript: true, // 允许页面执行 JavaScript 代码
      preload: path.join(__dirname, "preload.js"), // 在渲染进程执行之前,预加载指定的脚本
    },
  });


  // 加载 HTML 文件到窗口
  窗口.loadFile("index.html");
}

// 当应用程序准备就绪时执行以下操作
app.whenReady().then(() => {
  // 创建窗口
  创建窗口();

  // 当应用程序激活时,如果没有窗口存在,则创建一个新窗口
  app.on("activate",()=>
    if (BrowserWindow.getAllWindows().length === 0) 创建窗口();
});

// 4.当所有窗口关闭时,退出应用程序
app.on("window-all-closed", function () {
   app.quit();
});

4.准备预加载脚本——preload.js

const { contextBridge, ipcRenderer } = require("electron");

// 在主进程中,将"通讯API"暴露给渲染进程
contextBridge.exposeInMainWorld("通讯API", {
});

一.模式一:渲染进程向主进程通讯(单向)

html文件

 <div>
      <h3>模式 1:渲染器进程到主进程(单向)</h3>
      <p>
        要将单向 IPC 消息从渲染器进程发送到主进程,您可以使用 ipcRenderer.send
        API 发送消息,然后使用 ipcMain.on API 接收。
      </p>
      <div>
        <input type="text" id="title" placeholder="未选择时提示" />
        <button id="btn" type="button">单项传值</button>
      </div>
    </div>

renderer.js文件

//模式一
const 传值按钮 = document.getElementById("btn");  // 获取传值按钮元素
const 输入栏 = document.getElementById("title");  // 获取输入栏元素
传值按钮.addEventListener("click", () => {
    const title = 输入栏.value;  // 获取输入栏的值
    let 模式一API = window.通讯API.模式一;  // 获取模式一API
    模式一API(title);  // 调用模式一API,并传入标题参数
});

preload.js文件

contextBridge.exposeInMainWorld("通讯API", {
  // 模式一:将title发送给渲染进程
  模式一: (title) => ipcRenderer.send("渲向主单向", title),
});

main.js文件

// 定义函数,用于将标题设置到窗口中
function 单项渲向主(窗口, 标题) {
  console.log(标题);
  const 当前窗口参数 = 窗口.sender;
  const 当前窗口 = BrowserWindow.fromWebContents(当前窗口参数);
  当前窗口.setTitle(标题);
}
function 创建窗口() {
  const 窗口 = new BrowserWindow({....});

  // 监听渲染进程发送的消息,将标题设置到窗口中
  ipcMain.on("渲向主单向", 单项渲向主);

  // 加载 HTML 文件到窗口
  窗口.loadFile("index.html");
}

代码图片

image.png

输出前页面

image.png

输出后页面

image.png 可以看到页面的标题发生了改变,通讯完成

二.模式 2:渲染器进程到主进程(双向)

html文件

  <div>
      <h3>模式 2:渲染器进程到主进程(双向)</h3>
      <p>
        双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。
        这可以通过将 ipcRenderer.invoke 与 ipcMain.handle 搭配使用来完成。
      </p>
      <div>
        <button type="button" id="btn1">获取路径</button>
        File path: <strong id="filePath"></strong>
      </div>
    </div>

renderer.js文件

/// 模式二
const 交互按钮 = document.getElementById("btn1");  // 获取交互按钮元素
const 路径 = document.getElementById("filePath");  // 获取路径元素

交互按钮.addEventListener("click", async () => {
    let 模式二API = window.通讯API.模式二;  // 获取模式二API
    const 返回路径 = await 模式二API();  // 调用模式二API,并获取返回的路径
    路径.innerText = 返回路径;  // 将路径设置为返回的路径内容
});

preload.js文件

contextBridge.exposeInMainWorld("通讯API", {
   // 模式二:等待渲染进程调用"dialog:openFile"事件
  模式二: () => ipcRenderer.invoke("dialog:openFile"),
});

main.js文件

// 定义异步函数,用于选择文件
async function 选择文件() {
  const { canceled, filePaths } = await dialog.showOpenDialog();
  if (!canceled) {
    return filePaths[0];
  }
}
// 当应用程序准备就绪时执行以下操作
app.whenReady().then(() => {
  // 监听渲染进程发送的消息,并处理返回结果
  ipcMain.handle("dialog:openFile", 选择文件);

  // 创建窗口
  创建窗口();

  // 当应用程序激活时,如果没有窗口存在,则创建一个新窗口
  app.on("activate", ()=>
    if (BrowserWindow.getAllWindows().length === 0) 创建窗口();
});

代码图片

image.png

输出前页面

image.png 点击按钮选择文件

image.png

输出后页面

image.png 出现文件路径,渲染进程向主进程发起请求再返回数据

三.模式 3:主进程到渲染器进程(单向)

html文件

 <div>
      <h3>模式 3:主进程到渲染器进程</h3>
      <p>
        将消息从主进程发送到渲染器进程时,需要指定是哪一个渲染器接收消息。
        消息需要通过其 WebContents 实例发送到渲染器进程。 此 WebContents
        实例包含一个 send 方法,其使用方式与 ipcRenderer.send 相同。
      </p>
      <div>Current value: <span id="counter">0</span></div>
    </div>

renderer.js文件

// 模式三
const 数值 = document.getElementById("counter");  // 获取数值元素
window.通讯API.模式三((value) => {  // 注册模式三的回调函数,接收value参数
    console.log(value);  // 在控制台输出value值
    const 原值 = Number(数值.innerText);  // 获取数值元素的文本内容,并转为数字类型
    const 新值 = 原值 + value;  // 计算新的数值
    数值.innerText = 新值.toString();  // 将新的数值设置为数值元素的文本内容
});

preload.js文件

contextBridge.exposeInMainWorld("通讯API", {
   // 模式三:通过回调函数接收主进程发送的值
  模式三: (callback) =>
    ipcRenderer.on("主向渲单向", (_event, value) => callback(value)),
});

main.js文件

function 创建窗口() {
  const 窗口 = new BrowserWindow({...});

  // 创建自定义菜单
  const 自定义菜单 = Menu.buildFromTemplate([
    {
      label: app.name,
      submenu: [
        {
          click: () => 窗口.webContents.send("主向渲单向", 1), // 点击菜单项时,向渲染进程发送消息
          label: "加一",
        },
        {
          click: () => 窗口.webContents.send("主向渲单向", -1), // 点击菜单项时,向渲染进程发送消息
          label: "减一",
        },
      ],
    },
  ]);
  // 设置应用程序菜单
  Menu.setApplicationMenu(自定义菜单);

  // 加载 HTML 文件到窗口
  窗口.loadFile("index.html");

  // 打开开发者工具
  窗口.webContents.openDevTools();
}

代码图片

image.png

输出前页面

image.png 点击加减一

输出后页面

加一

image.png 减一

image.png