IPC 通信机制详解
前言
Electron 的主进程与渲染进程运行在不同的 JavaScript 上下文中,它们不能直接相互调用,必须通过 IPC(Inter-Process Communication,进程间通信)机制交换数据和命令。IPC 是 Electron 架构中最重要的机制之一,本文将全面讲解其使用方法和最佳实践。
IPC 模块概览
Electron 提供了两套 IPC API:
| API | 用途 | 特点 |
|---|---|---|
ipcMain | 主进程接收消息 | 主进程端 |
ipcRenderer | 渲染进程发送消息 | 渲染进程端 |
webContents | 主进程主动发消息给渲染进程 | 主进程端 |
通信模式分为两种:请求-响应模式(invoke/handle)和事件模式(send/on)。
invoke / handle:双向异步请求
基本用法
这是 Electron 推荐的首选通信模式,基于 Promise,支持双向异步通信:
// main.ts — 主进程:注册处理程序
import { ipcMain } from 'electron';
// 注册处理程序,channel 建议使用 'domain:action' 命名
ipcMain.handle('file:read', async (event, filePath: string) => {
const fs = require('fs');
try {
const content = fs.readFileSync(filePath, 'utf-8');
return { success: true, data: content };
} catch (error) {
return { success: false, error: (error as Error).message };
}
});
// 处理程序中禁止使用 event.sender
// 正确做法是通过返回值传递数据// renderer.ts — 渲染进程:调用处理程序
import { ipcRenderer } from 'electron';
async function readConfig() {
const result = await ipcRenderer.invoke('file:read', './config.json');
if (result.success) {
console.log('文件内容:', result.data);
} else {
console.error('读取失败:', result.error);
}
}为什么推荐 invoke/handle:
- 基于 Promise,代码更清晰,避免回调地狱
- 主进程返回的值直接作为 Promise 的 resolve 值
- 支持超时、取消等高级特性
错误处理
// main.ts — 抛出错误
ipcMain.handle('db:query', async (event, sql: string) => {
if (!sql) {
throw new Error('SQL 语句不能为空');
}
return db.query(sql);
});
// renderer.ts — 捕获错误
try {
const result = await ipcRenderer.invoke('db:query', 'SELECT * FROM users');
} catch (error) {
console.error('查询失败:', (error as Error).message);
}send / on:事件监听模式
渲染进程向主进程发送消息(fire-and-forget)
适用于不需要返回值的场景,如日志上报、用户行为统计:
// renderer.ts
function trackEvent(eventName: string, properties: Record<string, unknown>) {
ipcRenderer.send('analytics:track', eventName, properties);
}
// 主进程监听
ipcMain.on('analytics:track', (event, eventName, properties) => {
console.log('用户行为:', eventName, properties);
// 发送到分析服务
sendToAnalytics(eventName, properties);
});注意:send 是单向的,渲染进程不知道主进程是否收到。
主进程向渲染进程推送消息
应用场景:主进程检测到系统事件(如网络状态变化),通知渲染进程更新 UI:
// main.ts
function notifyRenderer(status: 'online' | 'offline') {
const windows = BrowserWindow.getAllWindows();
windows.forEach(win => {
win.webContents.send('network:status-change', status);
});
}
// 监听系统网络变化(示例)
import { net } from 'electron';
net.on('online', () => notifyRenderer('online'));
net.on('offline', () => notifyRenderer('offline'));// renderer.ts
ipcRenderer.on('network:status-change', (event, status) => {
updateNetworkIndicator(status); // 更新 UI
});
// 组件卸载时移除监听器,防止内存泄漏
import { onUnmounted } from 'vue';
onMounted(() => {
const handler = (_event: Electron.IpcRendererEvent, status: string) => {
updateNetworkIndicator(status);
};
ipcRenderer.on('network:status-change', handler);
onUnmounted(() => {
ipcRenderer.removeListener('network:status-change', handler);
});
});channel 命名规范
良好的 channel 命名能避免冲突,便于维护:
// 命名规范:'模块:操作'
// 模块 = 功能域(大写驼峰或 kebab-case)
// 操作 = 具体动作(动词)
// ✅ 推荐
ipcMain.handle('file:read', ...);
ipcMain.handle('file:write', ...);
ipcMain.on('analytics:track', ...);
ipcMain.handle('window:minimize', ...);
// ❌ 不推荐(易冲突,难以定位)
ipcMain.handle('read', ...);
ipcMain.handle('readFile', ...);
ipcMain.on('data', ...);进程间数据传输规则
可序列化的数据
以下数据可以通过 IPC 传递:
- 基本类型:
string、number、boolean、null、undefined - 数组和对象(JSON 可序列化)
- 满足特定条件的对象(拥有
toJSON方法)
不能直接传递的数据
// ❌ 这些不能直接通过 IPC 传递
ipcRenderer.send('window:set-title', windowInstance); // DOM 节点
ipcRenderer.send('file:process', fileBuffer); // 原生 Buffer(需转换)
ipcRenderer.send('app:send', classInstance); // 类实例
// ✅ 正确做法:传递可序列化数据
ipcRenderer.send('window:set-title', { title: '新标题' });
ipcRenderer.send('file:process', { path: '/path/to/file' });传输 Buffer 和文件
// main.ts — 读取文件后转为 Base64 传递
import { readFile } from 'fs/promises';
import { Buffer } from 'buffer';
ipcMain.handle('file:get-image', async (event, filePath: string) => {
const buffer = await readFile(filePath);
// 将 Buffer 转为 Base64(可序列化)
return buffer.toString('base64');
});
// renderer.ts
const base64 = await ipcRenderer.invoke('file:get-image', '/path/to/image.png');
const img = new Image();
img.src = `data:image/png;base64,${base64}`;
document.body.appendChild(img);使用 File 对象的正确方式
拖拽文件到 Electron 窗口时,获取的是 File 对象,它包含 path 属性指向本地路径:
// renderer.ts — 拖拽文件
dropZone.addEventListener('drop', async (e) => {
const files = e.dataTransfer?.files;
if (files && files.length > 0) {
const filePath = (files[0] as File & { path: string }).path;
// 通过 IPC 传递文件路径(不是 File 对象)
await ipcRenderer.invoke('file:process', filePath);
}
});返回值与 Promise 链
invoke/handle 的返回值支持链式调用:
// main.ts — 可以返回任何可序列化的值
ipcMain.handle('config:get-all', async () => {
return {
theme: 'dark',
language: 'zh-CN',
user: { id: 1, name: 'Alice' },
};
});
// renderer.ts — 链式使用
const config = await ipcRenderer.invoke('config:get-all');
const lang = config.language;主进程主动发消息给渲染进程
除 webContents.send 外,还可以使用 WebContents 的事件:
// main.ts
const win = new BrowserWindow({ width: 800, height: 600 });
// 等渲染进程加载完成后发送
win.webContents.on('did-finish-load', () => {
win.webContents.send('init:data', { userId: 123 });
});
// 网络进度通知
win.webContents.on('did-start-loading', () => showLoading());
win.webContents.on('did-stop-loading', () => hideLoading());常见陷阱
监听器未移除导致内存泄漏
// ❌ 错误:多次挂载同一监听器
onMounted(() => {
ipcRenderer.on('data:update', handler);
// 组件重新挂载时,handler 会被注册多次
});
// ✅ 正确:使用 once 或在 onUnmounted 中清理
ipcRenderer.once('data:update', handler); // 只监听一次
// 或
onUnmounted(() => {
ipcRenderer.removeListener('data:update', handler);
});contextIsolation 下的 event.sender
当 contextIsolation: true 时,event.sender 不可用:
// ❌ contextIsolation: true 时,以下代码无效
ipcMain.handle('user:get-info', (event) => {
const sender = event.sender; // 被禁用,访问会报错
});
// ✅ 通过 preload 暴露的 contextBridge 访问
// 详见下一篇文章「Preload 脚本与上下文隔离」小结
- invoke/handle 是 Electron IPC 的首选模式,基于 Promise,支持异步返回值
- send/on 适用于单向通知或 fire-and-forget 场景
- channel 命名推荐
'模块:操作'格式,避免冲突 - 渲染进程通过 IPC 传递可序列化数据,Buffer 等使用 Base64 转换
- 始终在组件卸载时移除 IPC 监听器,防止内存泄漏
下一篇文章我们将深入 Preload 脚本与上下文隔离,学习如何通过 contextBridge 安全地暴露 API,以及如何正确配置 nodeIntegration 和 contextIsolation。
评论
Written by
AI-Writer
Related Articles
Preload 脚本与上下文隔离
理解 Preload 脚本的执行时机、contextBridge 安全暴露 API、nodeIntegration 与 contextIsolation 配置组合,以及主进程与渲染进程的安全边界
Read MoreNode.js 原生模块调用
在 Electron 主进程中使用 npm 原生模块(sqlite3、sharp),Native Module 编译(node-gyp / electron-rebuild),纯 JS 替代方案以及多线程 Worker Threads 实践
Read MoreIPC 通信机制详解
深入理解 Electron 的 IPC 模块:invoke/handle 双向异步模式、ipcRenderer.on 事件监听、channel 命名规范、进程间数据传输规则
Read More