在 Electron 中,进程间通信(IPC)是构建功能丰富的桌面应用程序的关键部分。由于 Electron 的主进程和渲染器进程具有不同的职责,IPC 成为执行许多常见任务的唯一方法,例如从 UI 调用原生 API 或从原生菜单触发 Web 内容的更改 。Electron 中包含三种主要的 IPC 模式:
渲染器进程到主进程(单向)
实现从渲染器进程单向发送 IPC 消息到主进程。可以使用ipcRenderer.send
API 发送消息,然后使用ipcMain.on
API 接收。这种模式通常用于从 Web 内容调用主进程 API。其中ipcRenderer.send
需要通过预加载脚本 preload.js 进行调用,如设置窗口标题。
import { contextBridge, ipcRenderer } from 'electron' contextBridge.exposeInMainWorld('electronAPI', { setTitle: (title) => ipcRenderer.send('set-title', title), })
预加载脚本通过exposeInMainWorld
添加名为electronAPI
的对象到 Window 对象中,electronAPI
包含一个setTitle
方法,该方法使用ipcRenderer
向主进程发送一个消息,并且携带了title
参数。
import { app, BrowserWindow, ipcMain } from 'electron' app.whenReady().then(() => { ipcMain.on('set-title', (event, title) => { const webContents = event.sender const win = BrowserWindow.fromWebContents(webContents) win.setTitle(title) }) })
ipcMain.on
监听来自渲染进程的set-title
消息。当这个消息被接收时,执行以下操作:
- 从
event
对象获取发送消息的渲染进程的webContents
。 - 使用
BrowserWindow.fromWebContents(webContents)
找到与该webContents
相关联的BrowserWindow
实例。 - 使用
win.setTitle(title)
方法设置窗口的标题,其中title
是从渲染进程传递来的参数。
然后在页面中通过window.electronAPI.setTitle()
方法就可以发送消息给主线程修改标题了。
渲染器进程到主进程(双向)
双向 IPC 常用于从渲染器进程代码调用主进程模块并等待结果。通过ipcRenderer.invoke
与ipcMain.handle
结合使用来实现。例如,可以从渲染器进程打开一个原生的文件对话框,并返回所选文件的路径。
与上面一样,给 Window 对象添加方法:
contextBridge.exposeInMainWorld('electronAPI', { openFile: () => ipcRenderer.invoke('dialog:openFile'), })
在主进程中,使用ipcMain.handle
监听事件,事件回调函数返回值将会作为一个Promise
返回到ipcRender.invoke
的调用。
app.whenReady().then(() => { ipcMain.handle('dialog:openFile', async () => { const { canceled, filePaths } = await dialog.showOpenDialog({}) if (!canceled) { return filePaths[0] } }) })
在渲染进程中,通过预加载脚本注入的方法调用,就能够获取到所选文件的路径.
window.electronAPI.openFile().then((path) => { // 获取到路径处理 })
需要注意的是,Electron 的 IPC 实现使用 HTML 标准的结构化克隆算法来序列化进程之间传递的对象,这意味着只有某些类型的对象可以通过 IPC 通道传递。支持的类型如下:
因此,在处理返回不可序列化的数据时,使用ipcRenderer.invoke
和ipcMain.handle
可能就不太适合,如处理流(stream)类型的数据,不能通过返回ReadStream
对象或回调函数使页面持续接收数据,这个时候可能需要考虑主进程向渲染进程持续发送数据能力。
主进程到渲染器进程
在主进程中,你可以使用webContents.send
方法来向特定的渲染器进程发送信息。首先,你需要获取到渲染器进程的webContents
对象。这通常在BrowserWindow
对象创建后完成。
import { app, BrowserWindow, ipcMain } from 'electron' app.on('ready', () => { const mainWindow = new BrowserWindow({ // 窗口配置 }) // 加载页面... mainWindow.loadURL('file:///path/to/your/index.html') // 创建顶部菜单选项 Menu.setApplicationMenu( Menu.buildFromTemplate([ { label: app.name, submenu: [ { // 向渲染器进程发送消息 click: () => mainWindow.webContents.send('message-from-main', 'Hello from Main Process'), label: 'SendMessage', }, ], }, ]) ) })
可以在预加载脚本中使用ipcRenderer
来接收主进程发送的消息。
ipcRenderer.on('message-from-main', (event, message) => { console.log(message) // 输出 'Hello from Main Process' })
如果需要在页面中获取消息,就需要一个传入回调函数。还是给 Window 对象添加方法,这个方法参数传入一个回调函数。
contextBridge.exposeInMainWorld('electronAPI', { onMainMessage: (callback) => ipcRenderer.on('message-from-main', (event, message) => callback(message)), })
然后在页面中调用:
window.electronAPI.onMainMessage((message) => { // 可以在这里更新页面内容或执行其他操作 })
上面是直接通过BrowserWindow
实例化的窗口的webContents
对象发送消息的方式,而在处理ipcMain.on
或ipcMain.handle
事件时,可以通过事件对象 (event
) 来给对应窗口回复消息。如在ipcMain.on
事件处理中,通常使用event.sender.send
或event.reply
方法发送回复:
ipcMain.on('did-finish-load', (event) => { // event.sender 实际为当前 webContents 对象 event.sender.send('message-from-main', 'Hello from Main Process') // 通过 event.reply 给对应 webContents 发送消息 event.reply('message-from-main', 'Hello from Main Process') })
ipcMain.handle
事件中,不存在event.reply
方法,所以只能用event.sender.send
。