5. 进程通信:IPC
这是什么:IPC(Inter-Process Communication,进程间通信)= 渲染进程和主进程之间"打电话"的机制。
为什么用:第 3 章说了,界面(渲染进程)碰不到系统能力,能力都在主进程(管家)手里——界面想读文件、拿系统信息,就必须靠 IPC 传话。
如何用:三个文件各写几行,做一个「点按钮显示系统信息」的功能,把通话流程完整走一遍。
5.1 通话的三方
渲染进程 index.html/renderer.js 主进程 main.js
│ │
│ window.electronAPI.getSystemInfo() │
│ ─────────► preload.js 桥 ─────────► ipcMain.handle 接听
│ │
│ ◄────────── 返回结果 ──────────────┘
| 角色 | 文件 | 干什么 |
|---|---|---|
| 主进程 | main.js |
用 ipcMain.handle('频道名', ...) 接听某频道的请求 |
| 桥梁 | preload.js |
用 contextBridge 把指定频道白名单式暴露给界面 |
| 渲染进程 | renderer.js |
调桥梁给的函数 invoke,像调普通函数一样 |
5.2 主进程:开一个"频道"
main.js 更新为:
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.handle('get-system-info', () => {
return {
platform: process.platform,
electron: process.versions.electron,
node: process.versions.node,
chrome: process.versions.chrome,
}
})
createWindow()
})
app.on('window-all-closed', () => {
app.quit()
})
| 新增代码 | 作用 |
|---|---|
webPreferences.preload |
告诉窗口:每个渲染进程启动前先执行 preload.js |
ipcMain.handle('get-system-info', fn) |
接听 get-system-info 频道,fn 的返回值就是回复 |
path.join(__dirname, ...) |
__dirname = 当前文件所在目录,用它拼出绝对路径最稳妥 |
5.3 桥梁:preload.js
新建 preload.js:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
getSystemInfo: () => ipcRenderer.invoke('get-system-info'),
})
就 5 行,干两件事:
| 代码 | 作用 |
|---|---|
contextBridge.exposeInMainWorld('electronAPI', {...}) |
往界面世界挂一个全局对象 window.electronAPI |
ipcRenderer.invoke('get-system-info') |
界面一调它,就向主进程那个频道发起请求并等回复 |
🔑 安全要点:界面能干什么,完全由你在这里暴露了什么决定。没暴露的能力界面永远碰不到——这就是不开
nodeIntegration也能安全干活的原因。
5.4 渲染进程:像调普通函数一样用
index.html 的 <body> 换成:
<button id="btn-info">这台电脑用的什么内核?</button>
<pre id="out"></pre>
<script src="renderer.js"></script>
renderer.js:
document.getElementById('btn-info').addEventListener('click', async () => {
const info = await window.electronAPI.getSystemInfo()
document.getElementById('out').textContent =
`系统 ${info.platform} · Electron ${info.electron} · Node ${info.node} · Chromium ${info.chrome}`
})
npm start 后点按钮,你会看到:
系统 win32 · Electron 45.x.x · Node 22.x.x · Chromium 15x.x.x
注意 invoke 是异步的(返回 Promise),所以回调前加 async/await——和你写 fetch 的姿势一样。
5.5 带参数怎么传
invoke 的第二个参数起就是传给主进程的实参,handle 的回调第一个参数收下:
// 渲染进程
window.electronAPI.add(3, 4)
// preload.js
add: (a, b) => ipcRenderer.invoke('add', a, b)
// main.js
ipcMain.handle('add', (_event, a, b) => a + b)
参数和返回值都会被自动序列化,普通对象、数组、字符串随便传,函数和 DOM 节点传不过去。
❓ 常见翻车现场
Q1:window.electronAPI is undefined
原因:main.js 里忘了配 webPreferences.preload,桥根本没架。
解决:对照 5.2 补上 webPreferences 那四行,重启。
Q2:No handler registered for 'get-system-info'
原因:渲染进程 invoke 的频道名和 ipcMain.handle 注册的对不上(拼写、大小写、多空格)。
解决:两边频道名复制粘贴保持一致,频道名惯例用小写加短横线。
Q3:改了 preload.js 没效果
原因:preload 只在窗口创建时执行一次。
解决:Ctrl+C 后重新 npm start,别只刷新界面。
💡 动手改
- 把频道改名成
sys-info(preload、main 两处同步改),验证通话正常 - 仿照 5.5 写一个
sum频道:界面传数组,主进程求和后返回 - 在
handle回调里console.log一下,确认主进程日志出现在终端
📦 本章小结
- IPC 三方:
ipcMain.handle接听、preload架桥、ipcRenderer.invoke拨号 - preload 用
contextBridge.exposeInMainWorld白名单式暴露能力 invoke返回 Promise,配合async/await使用- 传对象数组都行,函数传不过去;频道名两边必须一字不差
下一章:原生能力 🐸