{T}

ipcRenderer

ipcRenderer 是 Electron 渲染进程中用于与主进程通信的模块。

注意

在现代 Electron 应用中,推荐通过 preload 脚本和 contextBridge 暴露安全的 IPC 接口,而不是直接在渲染进程中使用 ipcRenderer

基本用法

发送消息

javascript
const { ipcRenderer } = require('electron')

// 发送异步消息(不等待返回)
ipcRenderer.send('channel-name', data)

// 发送同步消息(阻塞等待返回)
const result = ipcRenderer.sendSync('sync-channel', data)

// 发送消息并等待 Promise 返回
const response = await ipcRenderer.invoke('async-channel', data)

接收消息

javascript
// 监听主进程消息
ipcRenderer.on('channel-name', (event, data) => {
  console.log('Received:', data)
})

// 只监听一次
ipcRenderer.once('channel-name', (event, data) => {
  console.log('Received once:', data)
})

主要方法

send(channel, ...args)

向主进程发送异步消息:

javascript
// 渲染进程
ipcRenderer.send('window:minimize')
ipcRenderer.send('file:save', { path: '/path/to/file', content: 'Hello' })

// 主进程
ipcMain.on('window:minimize', (event) => {
  BrowserWindow.fromWebContents(event.sender)?.minimize()
})

ipcMain.on('file:save', (event, { path, content }) => {
  fs.writeFileSync(path, content)
  // 发送回执
  event.reply('file:saved', { success: true })
})

invoke(channel, ...args)

发送消息并返回 Promise(推荐使用):

javascript
// 渲染进程
const files = await ipcRenderer.invoke('dialog:open', {
  filters: [{ name: 'Images', extensions: ['png', 'jpg'] }]
})

// 主进程
ipcMain.handle('dialog:open', async (event, options) => {
  const result = await dialog.showOpenDialog(options)
  return result.filePaths
})

sendSync(channel, ...args)

发送同步消息(不推荐,会阻塞渲染进程):

javascript
// 渲染进程
const isMaximized = ipcRenderer.sendSync('window:isMaximized')

// 主进程
ipcMain.on('window:isMaximized', (event) => {
  event.returnValue = BrowserWindow.fromWebContents(event.sender)?.isMaximized()
})

on(channel, listener)

监听主进程发送的消息:

javascript
ipcRenderer.on('update:progress', (event, progress) => {
  console.log(`Download progress: ${progress}%`)
})

once(channel, listener)

只监听一次:

javascript
ipcRenderer.once('app:ready', () => {
  console.log('App is ready')
})

removeListener(channel, listener)

移除监听器:

javascript
const handler = (event, data) => console.log(data)
ipcRenderer.on('channel', handler)

// 移除
ipcRenderer.removeListener('channel', handler)

removeAllListeners(channel)

移除所有监听器:

javascript
ipcRenderer.removeAllListeners('channel')

通过 Preload 使用(推荐)

javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  // invoke 方法
  openDialog: (options) => ipcRenderer.invoke('dialog:open', options),
  
  // send 方法
  minimizeWindow: () => ipcRenderer.send('window:minimize'),
  
  // on 方法(返回取消监听函数)
  onUpdateProgress: (callback) => {
    const handler = (event, progress) => callback(progress)
    ipcRenderer.on('update:progress', handler)
    return () => ipcRenderer.removeListener('update:progress', handler)
  }
})
javascript
// renderer.js
// 使用暴露的 API
const files = await window.electronAPI.openDialog({ properties: ['openFile'] })
window.electronAPI.minimizeWindow()

const unsubscribe = window.electronAPI.onUpdateProgress((progress) => {
  console.log(progress)
})
// 取消监听
unsubscribe()

事件对象

IPC 回调中的 event 对象包含以下属性:

javascript
ipcRenderer.on('channel', (event, data) => {
  event.senderId      // 发送者的 ID
  event.sender        // 发送消息的 WebContents
  event.ports         // MessagePort 数组
  
  // 发送回执
  event.reply('reply-channel', responseData)
  
  // 返回值(用于 sendSync)
  event.returnValue = result
})

MessageChannel 通信

使用 MessageChannel 进行双向通信:

javascript
// 渲染进程
const { port1, port2 } = new MessageChannel()

// 发送一个端口给主进程
ipcRenderer.postMessage('port', null, [port1])

// 使用另一个端口接收消息
port2.onmessage = (event) => {
  console.log('Received:', event.data)
}

// 发送消息
port2.postMessage('Hello from renderer')
javascript
// 主进程
ipcMain.on('port', (event) => {
  const [port] = event.ports
  port.onmessage = (e) => {
    console.log('Received:', e.data)
    port.postMessage('Hello from main')
  }
  port.start()
})

完整示例

javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron')

const ALLOWED_CHANNELS = {
  invoke: ['dialog:open', 'file:read', 'file:write'],
  send: ['window:minimize', 'window:maximize', 'window:close'],
  on: ['file:changed', 'update:available']
}

contextBridge.exposeInMainWorld('ipc', {
  invoke: (channel, ...args) => {
    if (!ALLOWED_CHANNELS.invoke.includes(channel)) {
      return Promise.reject(new Error(`Invalid channel: ${channel}`))
    }
    return ipcRenderer.invoke(channel, ...args)
  },
  
  send: (channel, ...args) => {
    if (!ALLOWED_CHANNELS.send.includes(channel)) {
      throw new Error(`Invalid channel: ${channel}`)
    }
    ipcRenderer.send(channel, ...args)
  },
  
  on: (channel, callback) => {
    if (!ALLOWED_CHANNELS.on.includes(channel)) {
      throw new Error(`Invalid channel: ${channel}`)
    }
    const handler = (event, ...args) => callback(...args)
    ipcRenderer.on(channel, handler)
    return () => ipcRenderer.removeListener(channel, handler)
  }
})

参考链接