Electron进程间通信最佳实践指南


概述

Electron 应用由主进程和渲染进程组成,它们运行在不同的进程中,需要通过 IPC (Inter-Process Communication) 进行通信。Electron 提供了三种主要的通信方式:

  1. sendSync & returnValue - 同步通信
  2. send & reply - 异步通信
  3. invoke & handle - 现代异步通信(推荐)

1. sendSync & returnValue - 同步通信

使用场景

  • 需要立即获取返回值的简单操作
  • 数据量小且处理时间短的场景

实现方式

主进程 (main/index.js):

const { ipcMain } = require("electron")

// 监听同步消息
ipcMain.on("sync-message", (event, data) => {
  console.log("收到同步消息:", data)

  // 处理数据
  const result = processData(data)

  // 返回结果
  event.returnValue = result
})

渲染进程 (preload/index.js):

const { ipcRenderer } = require("electron")

// 发送同步消息并获取返回值
const result = ipcRenderer.sendSync("sync-message", { id: 1, name: "test" })
console.log("同步返回结果:", result)

注意事项

  • ⚠️ 会阻塞渲染进程,可能导致界面卡顿
  • 仅适用于轻量级操作
  • 不推荐用于复杂或耗时的操作

2. send & reply - 异步通信

使用场景

  • 需要异步处理的复杂操作
  • 需要保持界面响应性的场景

实现方式

主进程 (main/index.js):

const { ipcMain } = require("electron")

// 监听异步消息
ipcMain.on("async-message", (event, data) => {
  console.log("收到异步消息:", data)

  // 异步处理数据
  processDataAsync(data)
    .then((result) => {
      // 回复结果
      event.reply("async-reply", { success: true, data: result })
    })
    .catch((error) => {
      event.reply("async-reply", { success: false, error: error.message })
    })
})

渲染进程 (preload/index.js):

const { ipcRenderer } = require("electron")

// 发送异步消息
ipcRenderer.send("async-message", { id: 1, name: "test" })

// 监听回复
ipcRenderer.on("async-reply", (event, result) => {
  if (result.success) {
    console.log("异步处理成功:", result.data)
  } else {
    console.error("异步处理失败:", result.error)
  }
})

注意事项

  • ✅ 不会阻塞渲染进程
  • 需要手动管理事件监听器
  • 可能出现内存泄漏风险

3. invoke & handle - 现代异步通信(推荐)

使用场景

  • 所有异步通信场景
  • 需要 Promise 支持的现代开发

实现方式

主进程 (main/index.js):

const { ipcMain } = require("electron")

// 注册处理器
ipcMain.handle("get-user-data", async (event, userId) => {
  try {
    const userData = await fetchUserData(userId)
    return { success: true, data: userData }
  } catch (error) {
    return { success: false, error: error.message }
  }
})

ipcMain.handle("save-user-data", async (event, userData) => {
  try {
    await saveUserData(userData)
    return { success: true }
  } catch (error) {
    return { success: false, error: error.message }
  }
})

预加载脚本 (preload/index.js):

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

// 暴露安全的 API 到渲染进程
contextBridge.exposeInMainWorld("electronAPI", {
  // 用户数据相关
  user: {
    getUserData: (userId) => ipcRenderer.invoke("get-user-data", userId),
    saveUserData: (userData) => ipcRenderer.invoke("save-user-data", userData),
  },

  // 主题相关
  theme: {
    isDarkMode: () => ipcRenderer.invoke("isDarkMode"),
    setTheme: (theme) => ipcRenderer.invoke("setTheme", theme),
  },

  // 窗口控制
  window: {
    minimize: () => ipcRenderer.invoke("window-minimize"),
    maximize: () => ipcRenderer.invoke("window-maximize"),
    close: () => ipcRenderer.invoke("window-close"),
  },
})

渲染进程 (renderer.js):

// 使用 async/await 调用
async function loadUserData(userId) {
  try {
    const result = await window.electronAPI.user.getUserData(userId)
    if (result.success) {
      console.log("用户数据:", result.data)
      return result.data
    } else {
      console.error("获取用户数据失败:", result.error)
    }
  } catch (error) {
    console.error("调用失败:", error)
  }
}

// 使用 Promise 调用
function saveUserData(userData) {
  window.electronAPI.user
    .saveUserData(userData)
    .then((result) => {
      if (result.success) {
        console.log("保存成功")
      } else {
        console.error("保存失败:", result.error)
      }
    })
    .catch((error) => {
      console.error("调用失败:", error)
    })
}

最佳实践建议

1. 安全性优先

// ✅ 推荐:使用 contextIsolation 和 preload 脚本
webPreferences: {
  contextIsolation: true,
  nodeIntegration: false,
  preload: path.join(__dirname, 'preload.js')
}

// ❌ 避免:直接暴露 ipcRenderer
// window.ipcRenderer = require('electron').ipcRenderer

2. 错误处理

// 主进程
ipcMain.handle("api-call", async (event, data) => {
  try {
    const result = await processData(data)
    return { success: true, data: result }
  } catch (error) {
    console.error("API 调用失败:", error)
    return { success: false, error: error.message }
  }
})

// 渲染进程
async function callAPI(data) {
  try {
    const result = await window.electronAPI.someMethod(data)
    if (result.success) {
      return result.data
    } else {
      throw new Error(result.error)
    }
  } catch (error) {
    console.error("调用失败:", error)
    // 显示用户友好的错误信息
    showErrorMessage(error.message)
  }
}

3. 类型安全(TypeScript)

// 定义 API 接口
interface ElectronAPI {
  user: {
    getUserData: (userId: string) => Promise<ApiResponse<UserData>>
    saveUserData: (userData: UserData) => Promise<ApiResponse<void>>
  }
  theme: {
    isDarkMode: () => Promise<boolean>
    setTheme: (theme: "light" | "dark" | "system") => Promise<boolean>
  }
}

declare global {
  interface Window {
    electronAPI: ElectronAPI
  }
}

4. 性能优化

// 避免频繁的 IPC 调用
let cachedTheme = null

async function getTheme() {
  if (cachedTheme === null) {
    cachedTheme = await window.electronAPI.theme.isDarkMode()
  }
  return cachedTheme
}

// 批量处理
async function batchProcess(items) {
  const promises = items.map((item) => window.electronAPI.processItem(item))
  return Promise.all(promises)
}

5. 事件清理

// 在组件卸载时清理事件监听器
class MyComponent {
  constructor() {
    this.handleThemeChange = this.handleThemeChange.bind(this)
    window.electronAPI.theme.onThemeChange(this.handleThemeChange)
  }

  destroy() {
    // 清理事件监听器
    window.electronAPI.theme.offThemeChange(this.handleThemeChange)
  }
}

总结

通信方式 适用场景 优点 缺点
sendSync & returnValue 轻量级同步操作 简单直接 阻塞渲染进程
send & reply 复杂异步操作 不阻塞界面 需要手动管理事件
invoke & handle 现代异步通信 Promise 支持,类型安全 需要预加载脚本

推荐使用顺序:

  1. invoke & handle - 首选,现代且安全
  2. send & reply - 复杂场景的备选方案
  3. sendSync & returnValue - 仅在必要时使用

通过遵循这些最佳实践,你可以构建出安全、高效且易于维护的 Electron 应用。


文章作者: 高红翔
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 高红翔 !
  目录