Electron中的IPC通讯总结

单向通讯( 渲染进程 -> 主进程 )


// 主进程 main.js
  ipcMain.on('set-title', (event, title) => {
  })

// 预加载脚本 preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
  setTitle: (title) => ipcRenderer.send('set-title', title)
})

// 实际调用,渲染进程
setButton.addEventListener('click', () => {
  const title = titleInput.value
  window.electronAPI.setTitle(title)
})


单向通讯( 主进程 -> 渲染进程 )

将消息从主进程发送到渲染器进程时,需要指定是哪一个渲染器接收消息。消息需要通过WebContents实例发送到渲染器进程。此WebContents实例包含一个send方法,其使用方式与ipcRenderer.send相同。

对于从主进程到渲染器进程的 IPC,没有与 ipcRenderer.invoke 等效的 API。 不过,您可以从 ipcRenderer.on 回调中将回复发送回主进程(通过 单向通讯 渲染进程 -> 主进程)

// 主进程 main.js
function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

	mainWindow.webContents.send('update-counter', 1)
  
  mainWindow.loadFile('index.html')
}

// 预加载脚本 preload.js 
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
})

// 实际调用,渲染进程
const counter = document.getElementById('counter')
window.electronAPI.onUpdateCounter((value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue.toString()
})

双向通讯(渲染进程 <-> 主进程)

// 方法1:渲染进程请求,主进程方法,并等待其结果;(ipcRenderer.invoke 和 ipcMain.handle 配对使用)
// 示例:从渲染器进程打开一个原生的文件对话框,并返回所选文件的路径。

// 主进程 main.js
async function handleFileOpen () {
  const { canceled, filePaths } = await dialog.showOpenDialog()
  if (!canceled) {
    return filePaths[0]
  }
}

ipcMain.handle('dialog:openFile', handleFileOpen)

// 预加载脚本 preload.js 
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
  openFile: () => ipcRenderer.invoke('dialog:openFile')
})

// 实际调用,渲染进程
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')
btn.addEventListener('click', async () => {
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})

// 方法2:  单向通信的 ipcRenderer.send API 也可用于双向通信。 这是在 Electron 7 之前通过 IPC 进行异步双向通信的推荐方式。

// 主进程 main.js
ipcMain.on('asynchronous-message', (event, arg) => {
  console.log(arg) // 在 Node 控制台中打印“ping”
  // 作用如同 `send`,但返回一个消息,到发送原始消息的渲染器
  event.reply('asynchronous-reply', 'pong')
})

// 预加载脚本  preload.js
const { ipcRenderer } = require('electron')
ipcRenderer.on('asynchronous-reply', (_event, arg) => {
  console.log(arg) // 在 DevTools 控制台中打印“pong”
})
ipcRenderer.send('asynchronous-message', 'ping')
// 方法3: ipcRenderer.sendSync API 向主进程发送消息,并 同步 等待响应。

// 主进程 main.js
const { ipcMain } = require('electron')
ipcMain.on('synchronous-message', (event, arg) => {
  console.log(arg) // 在 Node 控制台中打印“ping”
  event.returnValue = 'pong'
})

// 预加载脚本 preload.js
const { ipcRenderer } = require('electron')
const result = ipcRenderer.sendSync('synchronous-message', 'ping')
console.log(result) // 在 DevTools 控制台中打印“pong”

双向通讯(渲染进程 <-> 渲染进程)

没有直接的方法可以使用 ipcMainipcRenderer 模块在 Electron 中的渲染器进程之间发送消息。 为此,您有两种选择:

  • 将主进程作为渲染器之间的消息代理。 这需要将消息从一个渲染器发送到主进程,然后主进程将消息转发到另一个渲染器。
  • 将MessagePort从主进程传递到两个渲染进程。 这将允许在初始设置后渲染器之间直接进行通信。
// 在两个渲染进程之间建立 MessageChannel
// 在这个示例中,主进程设置了一个MessageChannel,然后将每个端口发送给不同的渲染进程。 这样可以让渲染进程彼此之间发送消息,而无需使用主进程作为中转。

// 主进程 main.js
const { BrowserWindow, app, MessageChannelMain } = require('electron')

app.whenReady().then(async () => {
  // 创建窗口
  const mainWindow = new BrowserWindow({
    show: false,
    webPreferences: {
      contextIsolation: false,
      preload: 'preloadMain.js'
    }
  })

  const secondaryWindow = new BrowserWindow({
    show: false,
    webPreferences: {
      contextIsolation: false,
      preload: 'preloadSecondary.js'
    }
  })

  // 建立通道
  const { port1, port2 } = new MessageChannelMain()

  // webContents准备就绪后,使用postMessage向每个webContents发送一个端口。
  mainWindow.once('ready-to-show', () => {
    mainWindow.webContents.postMessage('port', null, [port1])
  })

  secondaryWindow.once('ready-to-show', () => {
    secondaryWindow.webContents.postMessage('port', null, [port2])
  })
})

// 预加载脚本 preload.js
const { ipcRenderer } = require('electron')

ipcRenderer.on('port', e => {
  // 接收到端口,使其全局可用。
  window.electronMessagePort = e.ports[0]

  window.electronMessagePort.onmessage = messageEvent => {
    // 处理消息
  }
})
posted @ 2025-04-02 03:08  CD、小月  阅读(155)  评论(0)    收藏  举报