Electron 主进程和渲染进程如何通信?这篇讲清楚了

0 阅读4分钟

Electron IPC Main API 知识点总结

目录


1. IPC 通信概述

什么是 IPC?

IPC(Inter-Process Communication) 是 Electron 中主进程渲染进程之间通信的机制。

┌─────────────────────────────────────────────────────┐
│                    主进程 (Main)                    │
│                                                     │
│  const { ipcMain } = require('electron')            │
│                                                     │
│  ipcMain.handle('channel', handler)                │
│      ↓ 接收请求                                      │
│      ↓ 返回结果                                      │
└─────────────────────┬───────────────────────────────┘
                      │ IPC
┌─────────────────────↓───────────────────────────────┐
│                 渲染进程 (Renderer)                  │
│                                                     │
│  const { ipcRenderer } = require('electron')       │
│                                                     │
│  ipcRenderer.invoke('channel', ...args)            │
└─────────────────────────────────────────────────────┘

2. IPC 通信架构

通信方向

方向方法说明
渲染 → 主ipcRenderer.send()异步发送
渲染 → 主ipcRenderer.invoke()异步等待响应
主 → 渲染webContents.send()主进程主动推送
渲染 → 主 → 渲染event.reply()回复发送者

3. 监听方法

3.1 基础监听 ipcMain.on()

const { ipcMain } = require('electron')

ipcMain.on('message', (event, arg1, arg2) => {
  console.log('收到消息:', arg1, arg2)
})

3.2 一次性监听 ipcMain.once()

ipcMain.once('single-use', (event, data) => {
  console.log('只会触发一次')
})

3.3 移除监听

// 移除特定监听器
ipcMain.off('channel', handlerFunction)

// 移除 channel 的所有监听器
ipcMain.removeAllListeners('channel')

// 移除所有监听器
ipcMain.removeAllListeners()

4. 消息发送模式

4.1 同步消息(不推荐)

// 主进程
ipcMain.on('sync-message', (event) => {
  event.returnValue = '同步响应'  // 直接返回值
})
// 渲染进程
const result = ipcRenderer.sendSync('sync-message')
console.log(result)  // '同步响应'

⚠️ 注意:同步消息会阻塞渲染进程,建议使用 invoke/handle 模式

4.2 异步消息 + 回复

// 主进程
ipcMain.on('async-message', (event, data) => {
  console.log('收到:', data)
  
  // 回复发送者
  event.reply('response', '处理完成')
})
// 渲染进程
ipcRenderer.send('async-message', { name: 'test' })

ipcRenderer.on('response', (event, data) => {
  console.log('收到回复:', data)
})

4.3 从子窗口/iframe 发送

event.reply() 会自动处理从非主 frame 发送的消息:

// 主进程
ipcMain.on('from-iframe', (event) => {
  // event.sender 就是 WebContents
  // 可以发送到主 frame
  event.sender.send('to-main-frame', data)
})

5. invoke/handle 模式(推荐)⭐

这是最推荐的通信方式,支持 Promise。

主进程:定义处理程序

const { ipcMain } = require('electron')

ipcMain.handle('get-user-data', async (event, userId) => {
  // event: IpcMainInvokeEvent
  console.log('请求来自:', event.senderFrame.origin)
  
  const userData = await fetchUserFromDatabase(userId)
  return userData  // 返回值会作为 Promise 的结果
})

渲染进程:调用

const { ipcRenderer } = require('electron')

async function loadUser(userId) {
  try {
    const user = await ipcRenderer.invoke('get-user-data', userId)
    console.log('用户数据:', user)
  } catch (error) {
    console.error('获取失败:', error.message)
  }
}

单次 handle

// 只处理一次,然后自动移除
ipcMain.handleOnce('single-invoke', async (event) => {
  return { once: true }
})

移除 handle

ipcMain.removeHandler('get-user-data')

错误处理

// 主进程
ipcMain.handle('might-fail', async (event, data) => {
  try {
    const result = await riskyOperation(data)
    return result
  } catch (error) {
    throw new Error('操作失败: ' + error.message)
  }
})

// 渲染进程
const result = await ipcRenderer.invoke('might-fail', data)
// 如果主进程抛出异常,这里会 reject

6. IpcMainEvent 对象

监听器接收的 event 对象包含:

属性说明
event.sender发送消息的 WebContents
event.senderFrame发送消息的 Frame
event.reply()回复消息给发送者
event.returnValue设置同步消息的返回值
event.preventDefault()阻止默认行为

使用示例

ipcMain.on('some-event', (event, ...args) => {
  // 回复消息
  event.reply('response-channel', { status: 'ok' })
  
  // 获取发送者信息
  const frame = event.senderFrame
  console.log('来源:', frame?.origin)
  
  // 主动向发送者推送消息
  event.sender.send('push-message', '来自主进程')
})

7. 最佳实践

7.1 推荐:使用 invoke/handle

// ✅ 推荐:清晰的 Promise 风格
ipcMain.handle('fetch-data', async (event, id) => {
  return await database.query(id)
})

// ❌ 避免:同步阻塞
ipcMain.on('sync-fetch', (event) => {
  event.returnValue = database.querySync(id)
})

7.2 封装 IPC 调用

// preload.js - 安全暴露 API
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  // 单向通知
  send: (channel, data) => ipcRenderer.send(channel, data),
  
  // 监听消息
  on: (channel, callback) => {
    ipcRenderer.on(channel, (event, ...args) => callback(...args))
  },
  
  // 双向通信(推荐)
  invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args)
})
// 渲染进程中使用
// 安全的双向通信
const userData = await window.electronAPI.invoke('get-user', userId)

// 发送通知
window.electronAPI.send('user-logged-in', { userId })

7.3 命名规范

// 推荐:使用命名空间格式
'window:minimize'
'window:maximize'
'file:open'
'file:save'
'settings:get'
'settings:set'

// 避免:随意命名
'message'
'data'
'get'

7.4 错误处理

// 主进程
ipcMain.handle('risky-operation', async (event, data) => {
  if (!data) {
    throw new Error('数据不能为空')
  }
  // ... 业务逻辑
})

// 渲染进程
try {
  const result = await ipcRenderer.invoke('risky-operation', data)
} catch (error) {
  console.error('操作失败:', error.message)
}

7.5 清理监听器

// 组件卸载时清理
onUnmounted(() => {
  ipcRenderer.removeAllListeners('channel-name')
})

// 或使用 once 避免手动清理
ipcRenderer.once('one-time-event', (event, data) => {
  // 处理后自动移除
})

完整示例

主进程 (main.js)

const { app, BrowserWindow, ipcMain } = require('electron')

let mainWindow

app.whenReady().then(() => {
  mainWindow = new BrowserWindow()
  
  // 处理获取数据请求
  ipcMain.handle('get-config', async (event, key) => {
    return { theme: 'dark', language: 'zh-CN' }
  })
  
  // 处理保存数据
  ipcMain.handle('save-config', async (event, config) => {
    await saveToFile(config)
    return { success: true }
  })
  
  // 监听窗口控制
  ipcMain.on('window-minimize', () => {
    mainWindow.minimize()
  })
  
  ipcMain.on('window-maximize', () => {
    if (mainWindow.isMaximized()) {
      mainWindow.unmaximize()
    } else {
      mainWindow.maximize()
    }
  })
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

预加载脚本 (preload.js)

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  getConfig: (key) => ipcRenderer.invoke('get-config', key),
  saveConfig: (config) => ipcRenderer.invoke('save-config', config),
  minimize: () => ipcRenderer.send('window-minimize'),
  maximize: () => ipcRenderer.send('window-maximize'),
  onUpdate: (callback) => ipcRenderer.on('config-updated', callback)
})

渲染进程 (renderer.js)

// 获取配置
const config = await window.electronAPI.getConfig('theme')

// 保存配置
await window.electronAPI.saveConfig({ theme: 'light' })

// 窗口控制
window.electronAPI.minimize()
window.electronAPI.maximize()

// 监听更新
window.electronAPI.onUpdate((newConfig) => {
  console.log('配置已更新:', newConfig)
})

方法速查表

方法说明返回值
ipcMain.on(channel, fn)持续监听-
ipcMain.once(channel, fn)单次监听-
ipcMain.off(channel, fn)移除监听-
ipcMain.handle(channel, fn)处理 Invoke 请求(推荐)Promise
ipcMain.handleOnce(channel, fn)单次处理 InvokePromise
ipcMain.removeHandler(channel)移除处理程序-
ipcMain.removeAllListeners(channel)移除所有监听器-

文档基于 Electron v28+ IPC Main API 编写