Electron 中 IPC(进程间通信)模式

在 Electron 中,ipcRenderer.invoke/ipcMain.handle 和 ipcRenderer.send/ipcMain.on 是两种不同的 IPC(进程间通信)模式,主要区别在于 通信方向返回结果的方式

ipcRenderer.invoke/ipcMain.handle

渲染进程通过 ipcRenderer.invoke 向主进程发送通信请求,并异步等待主进程返回结果;主进程通过 ipcMain.handle 监听处理并返回一个 Promise 对象。该通信模式特点如下:

  1. 双向通信,主进程返回一个 Promise,可以通过 async/await 或 .then() 获取结果。
  2. 主进程通过 ipcMain.handle 处理请求,并直接返回结果。
  3. 适合需要等待主进程处理结果的场景(例如:读取文件、数据库操作)。
  4. 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 返回结果。该通信模式特点如下:

  1. 单向通信,发送后不等待主进程响应。
  2. 主进程通过 ipcMain.on 监听消息,需要手动通过 event.reply 返回结果(如果需要)。
  3. 适合不需要立即获取结果的场景(例如:通知主进程执行某个操作)。
// 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 使用不同的通道名)。

演示代码地址:https://hgithub.xyz/zhench0515/electron-ipc

posted @ 2025-03-13 22:25  老甄Home  阅读(220)  评论(0)    收藏  举报