Skip to content

IPC 通信

IPC 用于主进程和渲染进程通信。推荐把 IPC 当作本地 RPC 使用:每个 channel 都代表一个明确能力。

请求响应

主进程注册 handler:

ts
import { ipcMain } from 'electron'

ipcMain.handle('app:version', () => {
  return process.env.npm_package_version
})

Preload 暴露 API:

ts
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('appApi', {
  getVersion: () => ipcRenderer.invoke('app:version'),
})

渲染进程调用:

ts
const version = await window.appApi.getVersion()

事件推送

主进程向窗口推送事件:

ts
win.webContents.send('download:progress', { percent: 50 })

Preload 包装监听能力:

ts
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('downloadApi', {
  onProgress: (callback: (payload: { percent: number }) => void) => {
    const listener = (_event: Electron.IpcRendererEvent, payload: { percent: number }) => {
      callback(payload)
    }

    ipcRenderer.on('download:progress', listener)

    return () => {
      ipcRenderer.removeListener('download:progress', listener)
    }
  },
})

渲染进程使用:

ts
const off = window.downloadApi.onProgress((payload) => {
  console.log(payload.percent)
})

off()

Channel 命名

推荐使用命名空间:

bash
app:version
settings:get
settings:set
file:open
download:progress

避免使用:

bash
message
event
invoke
execute

channel 名称越泛,越容易演变成万能通道。

参数校验

不要信任渲染进程传入的参数。

ts
ipcMain.handle('settings:set', (_event, key: unknown, value: unknown) => {
  if (typeof key !== 'string' || key.length === 0) {
    throw new Error('Invalid key')
  }

  if (typeof value !== 'string') {
    throw new Error('Invalid value')
  }

  return settingsService.set(key, value)
})

如果参数结构复杂,可以使用 zodvalibot 等 schema 校验库。

封装建议

  • 不在渲染进程直接使用 ipcRenderer
  • 不暴露通用 send(channel, ...args)
  • 一个业务能力对应一个明确 API。
  • handler 内只做参数校验和服务调用,复杂逻辑放到 service。
  • 渲染进程拿到的是普通数据,不是主进程对象引用。

基于 MIT 许可发布