🐸 青蛙编程网
首页 / 前端 / Electron / 5. 进程通信:IPC

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,别只刷新界面。

💡 动手改

  1. 把频道改名成 sys-info(preload、main 两处同步改),验证通话正常
  2. 仿照 5.5 写一个 sum 频道:界面传数组,主进程求和后返回
  3. 在 handle 回调里 console.log 一下,确认主进程日志出现在终端

📦 本章小结

  • IPC 三方:ipcMain.handle 接听、preload 架桥、ipcRenderer.invoke 拨号
  • preload 用 contextBridge.exposeInMainWorld 白名单式暴露能力
  • invoke 返回 Promise,配合 async/await 使用
  • 传对象数组都行,函数传不过去;频道名两边必须一字不差

下一章:原生能力 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。