Inter-Process Communication进程间通信
Inter-process communication (IPC) is a key part of building feature-rich desktop applications in Electron. 进程间通信(IPC)是在Electron中构建功能丰富的桌面应用程序的关键部分。Because the main and renderer processes have different responsibilities in Electron's process model, IPC is the only way to perform many common tasks, such as calling a native API from your UI or triggering changes in your web contents from native menus.由于主进程和渲染器进程在Electron的进程模型中具有不同的职责,因此IPC是执行许多常见任务的唯一方法,例如从UI调用本机API或从本机菜单触发web内容的更改。
IPC channels渠道
In Electron, processes communicate by passing messages through developer-defined "channels" with the ipcMain and ipcRenderer modules. 在Electron中,进程通过开发人员定义的“通道”与ipcMain和ipcRenderer模块传递消息进行通信。These channels are arbitrary (you can name them anything you want) and bidirectional (you can use the same channel name for both modules).这些通道是任意的(可以任意命名)和双向的(可以对两个模块使用相同的通道名称)。
In this guide, we'll be going over some fundamental IPC patterns with concrete examples that you can use as a reference for your app code.在本指南中,我们将介绍一些基本的IPC模式和具体示例,您可以将其作为应用程序代码的参考。
Understanding context-isolated processes理解上下文隔离过程
Before proceeding to implementation details, you should be familiar with the idea of using a preload script to import Node.js and Electron modules in a context-isolated renderer process.在继续讨论实现细节之前,您应该熟悉使用预加载脚本在上下文隔离的渲染器进程中导入Node.js和Electron模块的想法。
For a full overview of Electron's process model, you can read the process model docs.有关Electron流程模型的完整概述,请阅读流程模型文档。For a primer into exposing APIs from your preload script using the有关使用contextBridge
module, check out the context isolation tutorial.contextBridge
模块从预加载脚本中公开API的入门知识,请参阅上下文隔离教程。
Pattern 1: Renderer to main (one-way)模式1:主渲染器(单向)
To fire a one-way IPC message from a renderer process to the main process, you can use the ipcRenderer.send API to send a message that is then received by the ipcMain.on API.要从渲染器进程向主进程发送单向IPC消息,可以使用ipcRenderer.send API发送一条消息,然后由ipcMain.on API接收。
You usually use this pattern to call a main process API from your web contents. 通常使用此模式从web内容调用主进程API。We'll demonstrate this pattern by creating a simple app that can programmatically change its window title.我们将通过创建一个简单的应用程序来演示此模式,该应用程序可以通过编程方式更改其窗口标题。
For this demo, you'll need to add code to your main process, your renderer process, and a preload script. 对于这个演示,您需要向主进程、渲染器进程和预加载脚本添加代码。The full code is below, but we'll be explaining each file individually in the following sections.完整的代码在下面,但我们将在下面的部分中单独解释每个文件。
- main.js
- preload.js
- index.html
- renderer.js
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- http://mdn.asprain.cn/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>
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
const title = titleInput.value
window.electronAPI.setTitle(title)
});
1. Listen for events with ipcMain.on
使用ipcMain.on
收听事件
ipcMain.on
In the main process, set an IPC listener on the 在主进程中,使用set-title
channel with the ipcMain.on
API:ipcMainon
API在set-title
频道上设置IPC侦听器:
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')
//...
function handleSetTitle (event, title) {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
}
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.on('set-title', handleSetTitle)
createWindow()
}
//...
The above 上面的handleSetTitle
callback has two parameters: an IpcMainEvent structure and a title
string. handleSetTitle
回调有两个参数:IpcMainEvent结构和title
字符串。Whenever a message comes through the 每当消息通过设置标题通道时,此函数将查找连接到消息发送者的BrowserWindow实例,并在其上使用set-title
channel, this function will find the BrowserWindow instance attached to the message sender and use the win.setTitle
API on it.win.setTitle
API。
Make sure you're loading the 确保您正在为以下步骤加载index.html
and preload.js
entry points for the following steps!index.html
和preload.js
入口点!
2. Expose ipcRenderer.send
via preload通过预加载公开ipcRenderer.send
ipcRenderer.send
via preloadTo send messages to the listener created above, you can use the 要向上面创建的侦听器发送消息,可以使用ipcRenderer.send
API. ipcRenderer.send
API。By default, the renderer process has no Node.js or Electron module access. 默认情况下,渲染器进程没有Node.js或Electron模块访问权限。As an app developer, you need to choose which APIs to expose from your preload script using the 作为应用程序开发人员,您需要选择使用contextBridge
API.contextBridge
API从预加载脚本中公开哪些API。
In your preload script, add the following code, which will expose a global 在预加载脚本中,添加以下代码,该代码将向渲染器进程公开全局window.electronAPI
variable to your renderer process.window.electronAPI
变量。
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})
At this point, you'll be able to use the 此时,您将能够在渲染器进程中使用window.electronAPI.setTitle()
function in the renderer process.window.electronAPI.setTitle()
函数。
We don't directly expose the whole 出于安全原因,我们不直接公开整个ipcRenderer.send
API for security reasons. ipcRenderer.send
API。Make sure to limit the renderer's access to Electron APIs as much as possible.确保尽可能限制渲染器对ElectronAPI的访问。
3. Build the renderer process UI构建渲染器进程UI
In our BrowserWindow's loaded HTML file, add a basic user interface consisting of a text input and a button:在浏览器窗口加载的HTML文件中,添加由文本输入和按钮组成的基本用户界面:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- http://mdn.asprain.cn/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>
To make these elements interactive, we'll be adding a few lines of code in the imported 为了使这些元素具有交互性,我们将在导入的renderer.js
file that leverages the window.electronAPI
functionality exposed from the preload script:renderer.js
文件中添加几行代码,以利用从预加载脚本中公开的window.electronAPI
功能:
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
const title = titleInput.value
window.electronAPI.setTitle(title)
});
At this point, your demo should be fully functional. 此时,您的演示应该功能齐全。Try using the input field and see what happens to your BrowserWindow title!尝试使用输入字段,查看浏览器窗口标题的变化!
Pattern 2: Renderer to main (two-way)模式2:主渲染器(双向)
A common application for two-way IPC is calling a main process module from your renderer process code and waiting for a result. 双向IPC的一个常见应用程序是从渲染器进程代码调用主进程模块并等待结果。This can be done by using ipcRenderer.invoke paired with ipcMain.handle.这可以通过使用ipcRenderer.invoke和ipcMain.handle来完成。
In the following example, we'll be opening a native file dialog from the renderer process and returning the selected file's path.在下面的示例中,我们将从渲染器进程打开一个本机文件对话框,并返回所选文件的路径。
For this demo, you'll need to add code to your main process, your renderer process, and a preload script. 对于这个演示,您需要向主进程、渲染器进程和预加载脚本添加代码。The full code is below, but we'll be explaining each file individually in the following sections.完整的代码在下面,但我们将在下面的部分中单独解释每个文件。
- main.js
- preload.js
- index.html
- renderer.js
const {app, BrowserWindow, ipcMain, dialog} = require('electron')
const path = require('path')
async function handleFileOpen() {
const { canceled, filePaths } = await dialog.showOpenDialog()
if (canceled) {
return
} else {
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()
})
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI',{
openFile: () => ipcRenderer.invoke('dialog:openFile')
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- http://mdn.asprain.cn/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>
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')
btn.addEventListener('click', async () => {
const filePath = await window.electronAPI.openFile()
filePathElement.innerText = filePath
})
1. Listen for events with ipcMain.handle
使用ipcMain.handle
侦听事件
ipcMain.handle
In the main process, we'll be creating a 在主进程中,我们将创建一个handleFileOpen()
function that calls dialog.showOpenDialog
and returns the value of the file path selected by the user. handleFileOpen()
函数,该函数调用dialog.showOpenDialog
并返回用户选择的文件路径的值。This function is used as a callback whenever an 每当从渲染器进程通过ipcRender.invoke
message is sent through the dialog:openFile
channel from the renderer process. dialog:openFile
通道发送ipcRender.invoke
消息时,此函数用作回调。The return value is then returned as a Promise to the original 然后返回值作为对原始invoke
call.invoke
的承诺返回。
const { BrowserWindow, dialog, ipcMain } = require('electron')
const path = require('path')
//...
async function handleFileOpen() {
const { canceled, filePaths } = await dialog.showOpenDialog()
if (canceled) {
return
} else {
return filePaths[0]
}
}
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}
app.whenReady(() => {
ipcMain.handle('dialog:openFile', handleFileOpen)
createWindow()
})
//...
The IPC通道名称上的dialog:
prefix on the IPC channel name has no effect on the code. dialog:
前缀对代码没有影响。It only serves as a namespace that helps with code readability.它仅用作帮助代码可读性的名称空间。
Make sure you're loading the 确保您正在为以下步骤加载index.html
and preload.js
entry points for the following steps!index.html
和preload.js
入口点!
2. Expose ipcRenderer.invoke
via preload通过预加载公开ipcRenderer.invoke
ipcRenderer.invoke
via preloadIn the preload script, we expose a one-line 在预加载脚本中,我们公开了一行openFile
function that calls and returns the value of ipcRenderer.invoke('dialog:openFile')
. openFile
函数,该函数调用并返回ipcRenderer.invoke('dialog:openFile')
的值。We'll be using this API in the next step to call the native dialog from our renderer's user interface.我们将在下一步中使用此API从渲染器的用户界面调用本机对话框。
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile')
})
We don't directly expose the whole 出于安全原因,我们不直接公开整个ipcRenderer.invoke
API for security reasons. ipcRenderer.invoke
API。Make sure to limit the renderer's access to Electron APIs as much as possible.确保尽可能限制渲染器对ElectronAPI的访问。
3. Build the renderer process UI构建渲染器进程UI
Finally, let's build the HTML file that we load into our BrowserWindow.最后,让我们构建加载到浏览器窗口中的HTML文件。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- http://mdn.asprain.cn/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>
The UI consists of a single UI由一个#btn
button element that will be used to trigger our preload API, and a #filePath
element that will be used to display the path of the selected file. #btn
按钮元素组成,该元素将用于触发预加载API,以及一个#filePath
元素,该元素用于显示所选文件的路径。Making these pieces work will take a few lines of code in the renderer process script:要使这些工作正常,需要在渲染器进程脚本中编写几行代码:
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')
btn.addEventListener('click', async () => {
const filePath = await window.electronAPI.openFile()
filePathElement.innerText = filePath
})
In the above snippet, we listen for clicks on the 在上面的代码片段中,我们监听点击#btn
button, and call our window.electronAPI.openFile()
API to activate the native Open File dialog. #btn
按钮,并调用window.electronAPI.openFile()
API来激活本机打开文件对话框。We then display the selected file path in the 然后在#filePath
element.#filePath
元素中显示所选文件路径。
Note: legacy approaches注:传统方法
The 在Electron 7中添加了ipcRenderer.invoke
API was added in Electron 7 as a developer-friendly way to tackle two-way IPC from the renderer process. ipcRenderer.invoke
API,这是一种开发人员友好的方式,用于处理渲染器进程中的双向IPC。However, there exist a couple alternative approaches to this IPC pattern.然而,对于这种IPC模式,存在两种替代方法。
We recommend using 我们建议尽可能使用ipcRenderer.invoke
whenever possible. ipcRenderer.invoke
。The following two-way renderer-to-main patterns are documented for historical purposes.以下主要模式的双向渲染器是出于历史目的而记录的。
For the following examples, we're calling 对于以下示例,我们直接从预加载脚本调用ipcRenderer
directly from the preload script to keep the code samples small.ipcRenderer
,以保持代码示例较小。
Using ipcRenderer.send
使用ipcRenderer.send
ipcRenderer.send
The 我们用于单向通信的ipcRenderer.send
API that we used for single-way communication can also be leveraged to perform two-way communication. ipcRenderer.send
API也可以用于执行双向通信。This was the recommended way for asynchronous two-way communication via IPC prior to Electron 7.在Electron 7之前,这是通过IPC进行异步双向通信的推荐方法。
// You can also put expose this code to the renderer
// process with the `contextBridge` API
const { ipcRenderer } = require('electron')
ipcRenderer.on('asynchronous-reply', (_event, arg) => {
console.log(arg) // prints "pong" in the DevTools console
})
ipcRenderer.send('asynchronous-message', 'ping')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping" in the Node console
// works like `send`, but returning a message back
// to the renderer that sent the original message
event.reply('asynchronous-reply', 'pong')
})
There are a couple downsides to this approach:这种方法有几个缺点:
You need to set up a second您需要设置第二个ipcRenderer.on
listener to handle the response in the renderer process.ipcRenderer.on
侦听器来处理渲染器进程中的响应。With使用invoke
, you get the response value returned as a Promise to the original API call.invoke
,您将获得作为对原始API调用的承诺返回的响应值。There's no obvious way to pair the没有明显的方法将asynchronous-reply
message to the originalasynchronous-message
one.asynchronous-reply
消息与原始异步消息配对。If you have very frequent messages going back and forth through these channels, you would need to add additional app code to track each call and response individually.如果您有非常频繁的消息通过这些渠道来回传递,您需要添加额外的应用程序代码来单独跟踪每个呼叫和响应。
Using 使用ipcRenderer.sendSync
The ipcRenderer.sendSync
API sends a message to the main process and waits synchronously for a response.ipcRenderer.sendSync
API向主进程发送消息,并同步等待响应。
const { ipcMain } = require('electron')
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping" in the Node console
event.returnValue = 'pong'
})
// You can also put expose this code to the renderer
// process with the `contextBridge` API
const { ipcRenderer } = require('electron')
const result = ipcRenderer.sendSync('synchronous-message', 'ping')
console.log(result) // prints "pong" in the DevTools console
The structure of this code is very similar to the 此代码的结构与invoke
model, but we recommend avoiding this API for performance reasons. invoke
模型非常相似,但出于性能原因,我们建议避免使用此API。Its synchronous nature means that it'll block the renderer process until a reply is received.它的同步特性意味着它将阻止渲染器进程,直到收到回复。
Pattern 3: Main to renderer模式3:主渲染器
When sending a message from the main process to a renderer process, you need to specify which renderer is receiving the message. 将消息从主进程发送到渲染器进程时,需要指定哪个渲染器正在接收消息。Messages need to be sent to a renderer process via its WebContents instance. 消息需要通过其WebContents实例发送到渲染器进程。This WebContents instance contains a send method that can be used in the same way as 该WebContents实例包含一个send方法,该方法的使用方式与ipcRenderer.send
.ipcRenderer.send
相同。
To demonstrate this pattern, we'll be building a number counter controlled by the native operating system menu.为了演示这种模式,我们将构建一个由本机操作系统菜单控制的数字计数器。
For this demo, you'll need to add code to your main process, your renderer process, and a preload script. 对于这个演示,您需要向主进程、渲染器进程和预加载脚本添加代码。The full code is below, but we'll be explaining each file individually in the following sections.完整的代码在下面,但我们将在下面的部分中单独解释每个文件。
- main.js
- preload.js
- index.html
- renderer.js
const {app, BrowserWindow, Menu, ipcMain} = require('electron')
const path = require('path')
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
const menu = Menu.buildFromTemplate([
{
label: app.name,
submenu: [
{
click: () => mainWindow.webContents.send('update-counter', 1),
label: 'Increment',
},
{
click: () => mainWindow.webContents.send('update-counter', -1),
label: 'Decrement',
}
]
}
])
Menu.setApplicationMenu(menu)
mainWindow.loadFile('index.html')
// Open the DevTools.
mainWindow.webContents.openDevTools()
}
app.whenReady().then(() => {
ipcMain.on('counter-value', (_event, value) => {
console.log(value) // will print value to Node console
})
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
handleCounter: (callback) => ipcRenderer.on('update-counter', callback)
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- http://mdn.asprain.cn/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Menu Counter</title>
</head>
<body>
Current value: <strong id="counter">0</strong>
<script src="./renderer.js"></script>
</body>
</html>
const counter = document.getElementById('counter')
window.electronAPI.handleCounter((event, value) => {
const oldValue = Number(counter.innerText)
const newValue = oldValue + value
counter.innerText = newValue
event.sender.send('counter-value', newValue)
})
1. Send messages with the webContents
module使用WebContents
模块发送消息
webContents
moduleFor this demo, we'll need to first build a custom menu in the main process using Electron's 对于本演示,我们首先需要使用Electron的Menu
module that uses the webContents.send
API to send an IPC message from the main process to the target renderer.Menu
模块在主进程中构建一个自定义菜单,该模块使用webContents.send
API将IPC消息从主进程发送到目标渲染器。
const {app, BrowserWindow, Menu, ipcMain} = require('electron')
const path = require('path')
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
const menu = Menu.buildFromTemplate([
{
label: app.name,
submenu: [
{
click: () => mainWindow.webContents.send('update-counter', 1),
label: 'Increment',
},
{
click: () => mainWindow.webContents.send('update-counter', -1),
label: 'Decrement',
}
]
}
])
Menu.setApplicationMenu(menu)
mainWindow.loadFile('index.html')
}
//...
For the purposes of the tutorial, it's important to note that the 在本教程中,需要注意的是,click
handler sends a message (either 1
or -1
) to the renderer process through the update-counter
channel.click
处理程序通过更新计数器通道向渲染器进程发送消息(1
或-1
)。
click: () => mainWindow.webContents.send('update-counter', -1)
Make sure you're loading the 确保您正在为以下步骤加载index.html
and preload.js
entry points for the following steps!index.html
和preload.js
入口点!
2. Expose ipcRenderer.on
via preload通过预加载公开ipcRenderer.on
ipcRenderer.on
via preloadLike in the previous renderer-to-main example, we use the 与前面的renderer-to-main示例一样,我们使用预加载脚本中的contextBridge
and ipcRenderer
modules in the preload script to expose IPC functionality to the renderer process:contextBridge
和ipcRenderer
模块向渲染器进程公开IPC功能:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
onUpdateCounter: (callback) => ipcRenderer.on('update-counter', callback)
})
After loading the preload script, your renderer process should have access to the 加载预加载脚本后,渲染器进程应该可以访问window.electronAPI.onUpdateCounter()
listener function.window.electronAPI.onUpdateCounter()
监听器函数。
We don't directly expose the whole 出于安全原因,我们不直接公开整个ipcRenderer.on
API for security reasons. ipcRenderer.on
API。Make sure to limit the renderer's access to Electron APIs as much as possible.确保尽可能限制渲染器对ElectronAPI的访问。
In the case of this minimal example, you can call 在这个最小的示例中,您可以直接在预加载脚本中调用ipcRenderer.on
directly in the preload script rather than exposing it over the context bridge.ipcRenderer.on
,而不是通过上下文桥公开它。
const { ipcRenderer } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
const counter = document.getElementById('counter')
ipcRenderer.on('update-counter', (_event, value) => {
const oldValue = Number(counter.innerText)
const newValue = oldValue + value
counter.innerText = newValue
})
})
However, this approach has limited flexibility compared to exposing your preload APIs over the context bridge, since your listener can't directly interact with your renderer code.然而,与通过上下文桥公开预加载API相比,这种方法的灵活性有限,因为监听器不能直接与呈现器代码交互。
3. Build the renderer process UI构建渲染器进程UI
To tie it all together, we'll create an interface in the loaded HTML file that contains a 为了将它们联系在一起,我们将在加载的HTML文件中创建一个接口,其中包含一个#counter
element that we'll use to display the values:#counter
元素,我们将使用它来显示值:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- http://mdn.asprain.cn/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Menu Counter</title>
</head>
<body>
Current value: <strong id="counter">0</strong>
<script src="./renderer.js"></script>
</body>
</html>
Finally, to make the values update in the HTML document, we'll add a few lines of DOM manipulation so that the value of the 最后,为了更新HTML文档中的值,我们将添加几行DOM操作,以便在每次触发#counter
element is updated whenever we fire an update-counter
event.update-counter
事件时更新#counter
元素的值。
const counter = document.getElementById('counter')
window.electronAPI.onUpdateCounter((_event, value) => {
const oldValue = Number(counter.innerText)
const newValue = oldValue + value
counter.innerText = newValue
})
In the above code, we're passing in a callback to the 在上面的代码中,我们将回调传递给从预加载脚本中公开的window.electronAPI.onUpdateCounter
function exposed from our preload script. window.electronAPI.onUpdateCounter
函数。The second 第二个value
parameter corresponds to the 1
or -1
we were passing in from the webContents.send
call from the native menu.value
参数对应于我们从本机菜单的webContents.send
调用传入的1
或-1
。
Optional: returning a reply可选:返回回复
There's no equivalent for 对于主到渲染器IPC,ipcRenderer.invoke
for main-to-renderer IPC. ipcRenderer.invoke
没有等价的方法。Instead, you can send a reply back to the main process from within the 相反,您可以从ipcRenderer.on
callback.ipcRenderer.on
回调中向主进程发送回复。
We can demonstrate this with slight modifications to the code from the previous example. 我们可以通过对前面示例中的代码稍加修改来演示这一点。In the renderer process, use the 在渲染器进程中,使用event
parameter to send a reply back to the main process through the counter-value
channel.event
参数通过counter-value
通道将回复发送回主进程。
const counter = document.getElementById('counter')
window.electronAPI.onUpdateCounter((event, value) => {
const oldValue = Number(counter.innerText)
const newValue = oldValue + value
counter.innerText = newValue
event.sender.send('counter-value', newValue)
})
In the main process, listen for 在主进程中,侦听counter-value
events and handle them appropriately.counter-value
事件并适当处理它们。
//...
ipcMain.on('counter-value', (_event, value) => {
console.log(value) // will print value to Node console
})
//...
Pattern 4: Renderer to renderer模式4:渲染器到渲染器
There's no direct way to send messages between renderer processes in Electron using the 使用ipcMain
and ipcRenderer
modules. ipcMain
和ipcRenderer
模块,无法直接在Electron中的渲染器进程之间发送消息。To achieve this, you have two options:为此,您有两种选择:
Use the main process as a message broker between renderers.将主进程用作呈现器之间的消息代理。This would involve sending a message from one renderer to the main process, which would forward the message to the other renderer.这将涉及将消息从一个渲染器发送到主进程,主进程将消息转发到另一个渲染程序。Pass a MessagePort from the main process to both renderers.将MessagePort从主进程传递到两个渲染器。This will allow direct communication between renderers after the initial setup.这将允许在初始设置后渲染器之间进行直接通信。
Object serialization对象序列化
Electron's IPC implementation uses the HTML standard Structured Clone Algorithm to serialize objects passed between processes, meaning that only certain types of objects can be passed through IPC channels.Electron的IPC实现使用HTML标准结构化克隆算法来序列化进程之间传递的对象,这意味着只有某些类型的对象可以通过IPC通道传递。
In particular, DOM objects (e.g. 特别是,DOM对象(如Element
, Location
and DOMMatrix
), Node.js objects backed by C++ classes (e.g. process.env
, some members of Stream
), and Electron objects backed by C++ classes (e.g. WebContents
, BrowserWindow
and WebFrame
) are not serializable with Structured Clone.Element
、Location
和DOMMatrix
)、C++类支持的Node.js对象(如process.env
、Stream
的某些成员)以及C++类(如WebContents
、BrowserWindow
和WebFrame
)支持的Electron对象不能使用结构化克隆串行化。