Electron 中 IPC(进程间通信)模式
在 Electron 中,ipcRenderer.invoke/ipcMain.handle 和 ipcRenderer.send/ipcMain.on 是两种不同的 IPC(进程间通信)模式,主要区别在于 通信方向 和 返回结果的方式。
ipcRenderer.invoke/ipcMain.handle
渲染进程通过 ipcRenderer.invoke 向主进程发送通信请求,并异步等待主进程返回结果;主进程通过 ipcMain.handle 监听处理并返回一个 Promise 对象。该通信模式特点如下:
- 双向通信,主进程返回一个 Promise,可以通过 async/await 或 .then() 获取结果。
- 主进程通过 ipcMain.handle 处理请求,并直接返回结果。
- 适合需要等待主进程处理结果的场景(例如:读取文件、数据库操作)。
- ipcRenderer.invoke 与 ipcMain.handle 一般成对使用。
// preload.js 预加载脚本
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('open-file'), // 渲染进程触发通信的频道名称open-file,主进程监听open-file频道并返回文件路径
})
// main.js 主进程
const { app, BrowserWindow, Menu, ipcMain, dialog } = require('electron/main')
const path = require('node:path')
// 创建浏览器窗口
const createWindow = () => {
const win = new BrowserWindow({
width: 800, // 弹窗宽度
height: 600, // 弹窗高度
webPreferences: {
// sandbox: false, // 是否开启沙盒模式
// nodeIntegration: false, // 是否开启node集成
// contextIsolation: true, // 是否开启上下文隔离
preload: path.join(__dirname, 'preload.js'), // 预加载脚本
},
})
// 把html文件加载到弹窗中
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
// 监听渲染进程的 open-file 频道,显示文件选择框
ipcMain.handle('open-file', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog()
if (canceled) {
return
} else {
return filePaths[0]
}
})
})
// render.js 渲染脚本
window.addEventListener('DOMContentLoaded', () => {
const electronAPI = window.electronAPI
if (electronAPI?.openFile) {
const openBtn = document.getElementById('open-btn')
const pathElement = document.getElementById('filePath')
openBtn.addEventListener('click', async () => {
const filePath = await electronAPI.openFile()
pathElement.innerText = filePath
})
}
})
ipcRenderer.send/ipcMain.on
渲染进程通过 ipcRenderer.send 向主进程发送单向消息,不期待主进程返回结果。主进程通过 ipcMain.on 监听消息,如果需要响应渲染进程,则需要手动通过 event.reply 返回结果。该通信模式特点如下:
- 单向通信,发送后不等待主进程响应。
- 主进程通过 ipcMain.on 监听消息,需要手动通过 event.reply 返回结果(如果需要)。
- 适合不需要立即获取结果的场景(例如:通知主进程执行某个操作)。
// preload.js 预加载脚本
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
send: (channel, data) => {
// 白名单通道
let validChannels = ['toMain']
if (validChannels.includes(channel)) {
// 渲染进程出发send,主进程返回toRenderer
ipcRenderer.send(channel, data)
}
},
})
// 渲染进程监听主进程返回的信息
ipcRenderer.on('toRenderer', (event, arg) => {
console.log(arg) // 打印主进程传递过来的参数
document.getElementById('toRender').innerText = arg
})
// main.js 主进程
const { app, BrowserWindow, Menu, ipcMain, dialog } = require('electron/main')
const path = require('node:path')
// 创建浏览器窗口
const createWindow = () => {
const win = new BrowserWindow({
width: 800, // 弹窗宽度
height: 600, // 弹窗高度
webPreferences: {
// sandbox: false, // 是否开启沙盒模式
// nodeIntegration: false, // 是否开启node集成
// contextIsolation: true, // 是否开启上下文隔离
preload: path.join(__dirname, 'preload.js'), // 预加载脚本
},
})
// 把html文件加载到弹窗中
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
// 主进程监听渲染进程的 toMain 频道,并返回 toRenderer 频道
ipcMain.on('toMain', (event, arg) => {
console.log(arg) // 打印渲染进程传递过来的参数
event.reply('toRenderer', "I'm fine, thank you.")
})
})
// render.js 渲染脚本
window.addEventListener('DOMContentLoaded', () => {
const setButton = document.getElementById('send-btn')
setButton.addEventListener('click', () => {
if (window.electronAPI?.send) {
window.electronAPI.send('toMain', 'How are you?')
}
})
})
// html
<div class="demo-5 demo-box">
<p>
向主进程发送消息:How are you?
<button type="button" id="send-btn">toMain</button>
</p>
<p>
主进程响应:
<span id="toRender"></span>
</p>
</div>
注意事项
版本兼容性:ipcRenderer.invoke 和 ipcMain.handle 从 Electron 7 开始支持。旧版本中可以使用 ipcRenderer.send + ipcMain.on + event.reply 实现类似功能。
错误处理:invoke/handle 的错误会自动传递到渲染进程的 Promise.catch 中。在 send/on 中需要手动处理错误。
通信通道命名:避免通道名(通信频道名称)冲突(例如:为 invoke 和 send 使用不同的通道名)。

浙公网安备 33010602011771号