Electron进程间通信IPC

时游大约 2 分钟

Electron 进程间通信(IPC)

通信模型

渲染进程出于安全被沙箱隔离,不能直接调用系统 API。所有系统能力(读写文件、弹窗、窗口控制等)都要:

渲染进程 → preload(contextBridge 暴露接口)→ ipcRenderer → 主进程 ipcMain 处理 → 返回结果

模式一:invoke / handle(推荐,请求-响应)

主进程 main.js

const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const path = require("path");
const fs = require("fs");

ipcMain.handle("dialog:openFile", async () => {
	const { canceled, filePaths } = await dialog.showOpenDialog();
	if (canceled) return null;
	return filePaths[0];
});

// 读取文件内容返回给渲染进程
ipcMain.handle("file:read", async (event, filePath) => {
	return fs.promises.readFile(filePath, "utf-8");
});

const createWindow = () => {
	const win = new BrowserWindow({
		width: 800,
		height: 600,
		webPreferences: {
			preload: path.join(__dirname, "preload.js")
		}
	});
	win.loadFile("index.html");
};

app.whenReady().then(createWindow);

预加载脚本 preload.js

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

// 只暴露白名单通道,不要直接把 ipcRenderer 整个暴露出去!
contextBridge.exposeInMainWorld("electronAPI", {
	openFile: () => ipcRenderer.invoke("dialog:openFile"),
	readFile: filePath => ipcRenderer.invoke("file:read", filePath)
});

渲染进程(页面中)

// 页面 JS 中直接使用 window.electronAPI
const btn = document.getElementById("openBtn");
btn.addEventListener("click", async () => {
	const filePath = await window.electronAPI.openFile();
	if (filePath) {
		const content = await window.electronAPI.readFile(filePath);
		console.log(content);
	}
});

模式二:send / on(单向消息,适合通知)

渲染进程 → 主进程

// preload.js
contextBridge.exposeInMainWorld("electronAPI", {
	sendMessage: message => ipcRenderer.send("message:send", message)
});

// 页面
window.electronAPI.sendMessage("hello main");
// main.js
ipcMain.on("message:send", (event, message) => {
	console.log("收到渲染进程消息:", message);
	// event.sender 就是谁发来的 webContents,可以直接回复
	event.sender.send("message:reply", "received: " + message);
});

主进程 → 渲染进程(主动推送)

// 主进程:向指定窗口推送消息(如下载进度)
win.webContents.send("download:progress", 45);
// preload.js:暴露订阅接口
contextBridge.exposeInMainWorld("electronAPI", {
	onDownloadProgress: callback =>
		ipcRenderer.on("download:progress", (event, percent) => callback(percent))
});

// 页面
window.electronAPI.onDownloadProgress(percent => {
	console.log("下载进度:", percent);
});

Demo:渲染进程控制窗口

main.js

ipcMain.on("window:minimize", event => {
	BrowserWindow.fromWebContents(event.sender)?.minimize();
});
ipcMain.on("window:maximize", event => {
	const win = BrowserWindow.fromWebContents(event.sender);
	win?.isMaximized() ? win.unmaximize() : win?.maximize();
});
ipcMain.on("window:close", event => {
	BrowserWindow.fromWebContents(event.sender)?.close();
});

preload.js

contextBridge.exposeInMainWorld("electronAPI", {
	minimize: () => ipcRenderer.send("window:minimize"),
	maximize: () => ipcRenderer.send("window:maximize"),
	close: () => ipcRenderer.send("window:close")
});

自定义标题栏按钮(页面)

<div class="titlebar">
	<button onclick="window.electronAPI.minimize()">—</button>
	<button onclick="window.electronAPI.maximize()">□</button>
	<button onclick="window.electronAPI.close()">×</button>
</div>

安全清单

  1. contextIsolation 保持开启(默认),禁止 nodeIntegration: true
  2. preload 中白名单式暴露方法,不要 exposeInMainWorld("ipc", ipcRenderer)
  3. ipcMain 校验来自渲染进程的参数(渲染进程可能被 XSS 注入)
  4. win.loadURL 只加载可信页面,will-navigate 事件中拦截外部跳转
  5. 新版 Electron 建议开启 sandbox: true
Loading...