渲染进程与主进程通讯的三种模式
在 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");
}
代码图片
输出前页面
输出后页面
可以看到页面的标题发生了改变,通讯完成
二.模式 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) 创建窗口();
});
代码图片
输出前页面
点击按钮选择文件
输出后页面
出现文件路径,渲染进程向主进程发起请求再返回数据
三.模式 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();
}
代码图片
输出前页面
点击加减一
输出后页面
加一
减一