每个 Electron 应用都有一个单一的主进程,BrowserWindow 类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。本章我们来介绍下,如何在主进程和渲染器进程之间传递消息
渲染器向主进程发送消息
invoke / handle
渲染进程通过 invoke 向主进程发起异步调用,主进程通过 handle 处理并返回 promise
Electron 的 IPC 通信底层基于「通道名 - 数据」的映射机制,通道名是唯一标识通信逻辑的键
javascript
ipcRenderer.invoke("renderer-invoke", message)
javascript
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('node:path');
let window;
app.on('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html');
});
ipcMain.handle("renderer-invoke", (event, message) => {
console.log("main receive from renderer: ", message);
return "hello world"
});
我们定义了一个 "renderer-invoke" 通道名,渲染进程通过这个通道名给主进程发送消息,主进程输出消息,并返回 "hello world"。
我们将 ipcRenderer.invoke 通过预加载脚本暴露给渲染进程。
javascript
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('messageApi', {
send :(message) => ipcRenderer.invoke("renderer-invoke", message)
});
渲染进程调用发送消息接口
javascript
async function send () {
const textContainer = document.getElementById('text-container');
const result = await window.messageApi.send("hello");
textContainer.textContent = result;
}
send()
html 显示
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>消息通信示例</title>
</head>
<body>
<h1>Hello Electron!</h1>
<div id="text-container"></div>
<script src="./renderer.js"> </script>
</body>
</html>
npm start 运行,我们可以看到终端输出了,main receive from renderer: hello,界面上显示主进程返回的字符串 hello world。
send / on
send / on 用于单向通信,这里的单向指的是渲染进程不能通过像 invoke 那样获取主进程返回的结果,但是我们仍可以通过 event.reply 将结果发送给渲染进程。
同样的,我们调用 ipcRenderer.send 接口发送消息,使用预处理脚本暴露接口。
javascript
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('messageApi', {
sendMessage: (message) => ipcRenderer.send("renderer-main", message),
onReceiveMessage: (callback) => {
ipcRenderer.on("main-renderer", (_event, message) => {
callback(message);
});
}
});
ipcRenderer.on 用于接收主进程的返回值,后面,渲染进程接收主进程发送的消息也是用同样的接口。
javascript
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('node:path');
let window;
app.on('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html');
ipcMain.on("renderer-main", (event, message) => {
console.log("main receive from renderer", message);
event.reply("main-renderer", "received");
});
主进程接收消息,并返回 received,其实从这里可以看出,send / on 的返回更像是主进程收到消息后,主动又发了一个新的消息。
javascript
window.messageApi.onReceiveMessage((message) => {
const textContainer = document.getElementById('text-container');
textContainer.textContent = message;
});
window.messageApi.sendMessage("hello");
renderer 进程注册回调函数处理收到的消息,然后发送消息。
sendSync / on
ipcRenderer.sendSync API 向主进程发送消息,并同步 等待响应。它会阻塞渲染进程,直至主进程返回
预加载脚本暴露接口
javascript
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('messageApi', {
syncSendMessage: (message) => ipcRenderer.sendSync("synchronous-message", message)
});
主进程回调处理
javascript
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('node:path');
let window;
app.on('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html');
});
ipcMain.on("synchronous-message", (event, message) => {
console.log("main receive from renderer", message);
event.returnValue = "synchronous";
});
渲染进程发送消息,显示结果
javascript
const textContainer = document.getElementById('text-container');
const result = window.messageApi.syncSendMessage("hello")
textContainer.textContent = result;
主进程向渲染进程发送消息
主进程可以通过 webContents.send api 主动向渲染进程发送消息
javascript
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('node:path');
let window;
app.on('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html');
window.webContents.on('did-finish-load', () => {
window.webContents.send("main-renderer", "hello");
});
});
由于渲染进程可以有多个,所以不是像 ipcRenderer.send 那样发送,而是通过 BrowserWindow 的 webContents.send 发送消息,其他部分和渲染进程向主进程发送消息几乎一致。
javascript
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('messageApi', {
onReceiveMessage: (callback) => {
ipcRenderer.on("main-renderer", (_event, message) => {
callback(message);
});
}
});
预加载脚本暴露接口
cpp
window.messageApi.onReceiveMessage((message) => {
const textContainer = document.getElementById('text-container');
textContainer.textContent = message;
});
渲染进程接收消息
渲染进程向渲染进程发送消息
ipcMain 和 ipcRenderer 模块无法在渲染进程之间发送消息,我们可以通过主进程进行中转,又或者通过 MessageChannelMain 模块来实现,这部分我们在之后的章节再详细介绍。