进程间的通信
进程间通信 (IPC) 是在 Electron 中构建功能丰富的桌面应用程序的关键部分之一。 由于主进程和渲染器进程在 Electron 的进程模型具有不同的职责,因此 IPC 是执行许多常见任务的唯一方法,例如从 UI 调用原生 API 或从原生菜单触发 Web 内容的更改。
IPC 通道
- 在 Electron 中,进程使用 ipcMain 和 ipcRenderer 模块,通过开发人员定义的“通道”传递消息来进行通信。 这些通道是 任意 (您可以随意命名它们)和 双向 (您可以在两个模块中使用相同的通道名称)的。
- 在本指南中,我们将介绍一些基本的 IPC 模式,并提供具体的示例。您可以将这些示例作为您应用程序代码的参考。
了解上下文隔离进程
- 在开始实现细节之前,您应该熟悉使用 预加载脚本 在上下文隔离渲染器进程中导入 Node.js 和 Electron 模块的概念。
- 有关 Electron 进程模型的完整概述,您可以阅读 进程模型文档。
- 有关使用 contextBridge 模块从预加载脚本暴露 API 的入门知识,请查看 上下文隔离教程。
模式1:渲染器进程到主进程(单向)
- 要将单向 IPC 消息从渲染器进程发送到主进程,您可以使用 ipcRenderer.send API 发送消息,然后使用 ipcMain.on API 接收。
- 通常使用此模式从 Web 内容调用主进程 API。 我们将通过创建一个简单的应用来演示此模式,可以通过编程方式更改它的窗口标题。
- 对于此演示,您需要将代码添加到主进程、渲染器进程和预加载脚本。 完整代码如下,我们将在后续章节中对每个文件进行单独解释。
-
Example
const { app, BrowserWindow, ipcMain } = require('electron/main') const path = require('node:path') function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { // __dirname: 字符串指向当前正在执行脚本的路径 (在本例中,它指向你的项目的根文件夹)。 // path.join: API 将多个路径联结在一起,创建一个跨平台的路径字符串。 preload: path.join(__dirname, 'preload.js') } }) // 监听 ipcRenderer.send 发送的 set-title 事件 ipcMain.on('set-title', (event, title) => { const webContents = event.sender const win = BrowserWindow.fromWebContents(webContents) win.setTitle(title) }) // 加载 index.html mainWindow.loadFile('index.html') } // 这段程序将会在 Electron 结束初始化 // 和创建浏览器窗口的时候调用 // 部分 API 在 ready 事件触发后才能使用。 app.whenReady().then(() => { createWindow() // 在 macOS 系统内, 如果没有已开启的应用窗口 // 点击托盘图标时通常会重新创建一个新窗口 app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) }) // 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此, 通常 // 对应用程序和它们的菜单栏来说应该时刻保持激活状态, // 直到用户使用 Cmd + Q 明确退出 app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit() })
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> <title>Hello World!</title> </head> <body> Title: <input id="title"/> <button id="btn" type="button">Set</button> <script src="./renderer.js"></script> </body> </html>
模式2:渲染器进程到主进程(双向)
- 双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。 这可以通过将 ipcRenderer.invoke 与 ipcMain.handle 搭配使用来完成。
- 在下面的示例中,我们将从渲染器进程打开一个原生的文件对话框,并返回所选文件的路径。
- 对于此演示,您需要将代码添加到主进程、渲染器进程和预加载脚本。 完整代码如下,我们将在后续章节中对每个文件进行单独解释。
-
Example
const { app, BrowserWindow, ipcMain, dialog } = require('electron/main') const path = require('node:path') async function handleFileOpen () { const { canceled, filePaths } = await dialog.showOpenDialog() if (!canceled) { return filePaths[0] } } function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }) mainWindow.loadFile('index.html') } app.whenReady().then(() => { ipcMain.handle('dialog:openFile', handleFileOpen) createWindow() app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) }) app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit() })
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> <title>Dialog</title> </head> <body> <button type="button" id="btn">Open a File</button> File path: <strong id="filePath"></strong> <script src='./renderer.js'></script> </body> </html>
- 代码释义
-
使用 ipcMain.handle 监听事件 在主进程中,我们将创建一个
handleFileOpen()
函数,它调用dialog.showOpenDialog
并返回用户选择的文件路径值。 每当渲染器进程通过dialog:openFile
通道发送ipcRender.invoke
消息时,此函数被用作一个回调。 然后,返回值将作为一个Promise
返回到最初的invoke
调用。
-