预加载(preload)脚本只能访问部分 Node.js API,但是主进程可以访问全部API。此时,需要使用进程通信。
比如,在preload.js
中,不能访问__dirname
,不能使用 Node
中的 fs
模块,但主进程(main.js
)是可以的,这时候就需要使用进程通信。也就是说:要让 preload.js
通知 main.js
,去调用 Node.js API 实现功能。
Electron 进程通信:
- IPC:全称是 InterProcess Communication ,即:进程间通信。
- IPC连接不同进程:在 Electron 应用中,主进程和渲染进程各自有不同的职责和运行环境。IPC是它们之间进行交互和协同工作的关键机制。没有有效的 IPC,主进程和渲染进程将无法有效地沟通和协调,导致应用功能受限。
- 调用原生 API 的唯一途径 :IPC 是 Electron 中最为核心的内容。IPC 是从 UI(通常在渲染进程中)调用原生 API 的唯一方法。这意味着如果想要在 Electron 应用中实现诸如文件系统操作、与操作系统交互等功能,必须通过 IPC 让主进程执行这些操作,然后将结果返回给渲染进程进行展示或进一步处理。
- Electron 提供了
ipcMain
和ipcRenderer
模块用于主进程和渲染进程之间的通信。-
ipcMain
(主进程中使用,从主进程到渲染进程的异步通信。):- 发送消息时,事件名称为
channel
,即 通信通道。 - 回复同步信息时,需要设置
event.returnValue
。 - 可以使用
event.reply(...)
将异步消息发送回发送者。
- 发送消息时,事件名称为
-
ipcRenderer
(渲染进程中使用,从渲染器进程到主进程的异步通信。):
-
模式1:渲染器进程到主进程(单向)
-
概述:要将单向 IPC 消息从渲染器进程 发送到主进程 ,需要在渲染器进程 中
ipcRenderer.send
发送消息,在主进程 中使⽤ipcMain.on
接收消息。 -
常用于:从 Web 内容调用主进程 API。
-
ipcRenderer.send(channel,...args)
- 用于从渲染进程向主进程发送消息。
channel
:字符串,表示消息的通道名称。主进程可以通过监听这个特定的通道名称来接收来自渲染进程的消息。例如:可以定义一个名为'custom-action'
的通道。...args
:这是一个可变参数,可以传递任意数量的参数。这些参数可以是任何有效的 JavaScript 值,如字符串、数字、对象等。- 用于单向通信的
ipcRenderer.send
API 也可用于双向通信。在 Electron 7 之前,官方推荐使用ipcRenderer.send
进行异步双向通信。在 Electron 7 中,官方添加ipcRenderer.invoke
API,用于处理渲染器进程中双向 IPC。
-
- 在主进程中监听来自渲染进程的特定通道消息。
hannel
:这是一个字符串,表示特定的通信通道名称。渲染进程使用相同的通道名称通过ipcRenderer.send
或ipcRenderer.invoke
向主进程发送消息。listener
:这是一个回调函数,当主进程接收到来自渲染进程通过指定通道发送的消息时,这个回调函数会被执行。- 回调函数通常接受两个参数:
event
和args
。event
代表事件对象,可用于回复渲染进程等操作;args
是渲染进程发送的参数,可以是单个值或一组值,具体取决于渲染进程的发送方式。
- 回调函数通常接受两个参数:
示例:
实现需求:点击按钮后,在⽤户的 D 盘创建⼀个 hello.txt ⽂件,⽂件内容来⾃于⽤户输⼊。
- 在页面中添加相关元素
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
/>
<title>Electron</title>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<input id="content" type="text"><br><br>
<button id="btn">在⽤户的D盘创建⼀个hello.txt</button>
</body>
<script src="./render.js"></script>
</html>
- 在
preload.js
中使⽤ipcRenderer.send('信道', 参数)
发送消息,与主进程通信。
typescript
// contextBridge:在隔离的上下文中创建一个安全的、双向的、同步的桥梁。
// ipcRenderer是渲染进程中用于与主进程进行通信的模块。
const {contextBridge, ipcRenderer} = require('electron')
// 把方法/数据暴露给渲染进程
contextBridge.exposeInMainWorld('customAPI', {
saveFile: data => {
// 渲染进程给主进程发送⼀个消息
ipcRenderer.send('save-file', data)
}
})
- 在
render.js
中,调用preload.js
暴露的saveFile
方法,通知主进程
typescript
const btn = document.getElementById('btn');
const content = document.getElementById('content');
btn.addEventListener('click', () => {
// 在render.js 调用 preload.js 暴露出来的saveFile方法,通知主进程
customAPI.saveFile(content.value);
});
- 主进程中,在加载页面之前,使用
ipcMain.on('信道', 回调)
配置对应回调函数,接收
消息。
typescript
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path')
const fs = require('fs')
// 保存文件
function saveFile(event,data) {
console.log(event, data)
fs.writeFileSync('D:/hello.txt',data)
// 通过 'response-save-file' 通道将结果回复给渲染进程。
event.reply('response-save-file', '保存文件啦!');
}
function createWindow() {
const win = new BrowserWindow({
width: 1000, // 窗口宽度
height: 800, // 窗口高度
webPreferences: {
// 此处只能使用绝对路径
preload: path.join(__dirname, 'preload.js')
}
});
// 主进程注册对应回调
ipcMain.on('save-file',saveFile)
// 在窗口中加载一个远程页面
win.loadFile('./pages/index.html');
}
渲染进程与主进程通信的流程
- 渲染进程触发:
- 当渲染进程中的某个事件发生时,比如用户点击按钮或页面加载完成,可以调用
ipcRenderer.send('信道', 参数)
方法向主进程发送消息。
- 当渲染进程中的某个事件发生时,比如用户点击按钮或页面加载完成,可以调用
- 主进程接收和处理:
- 主进程使用
ipcMain.on('信道', 回调)
方法监听特定的通道名称。当主进程接收到来自渲染进程的消息时,它可以根据通道名称和传递的参数执行相应的操作。
- 主进程使用
- 渲染进程接收回复:
- 渲染进程可以使用
ipcRenderer.on
方法监听主进程的回复通道。当渲染进程接收到主进程的回复时,可以根据结果进行界面更新或其他处理。
- 渲染进程可以使用
模式2:渲染器进程到主进程(双向)
概述:渲染进程通过 ipcRenderer.invoke
发送消息,主进程使⽤ ipcMain.handle
接收并处理消
息。
常用于:从渲染器进程代码调用主进程模块并等待结果 。
ipcRenderer.invoke(channel, ...args)
channel
:这是一个字符串,表示消息的通道名称。与ipcRenderer.send
类似,主进程通过监听这个特定的通道名称来接收来自渲染进程的请求。...args
:这是一个可变参数,可以传递任意数量的参数给主进程。这些参数可以是任何有效的 JavaScript 值,用于向主进程提供请求所需的信息。ipcRender.invoke
的返回值是 Promise 实例。
ipcMain.handle(channel, listener)
channel
:一个字符串,代表特定的通信通道名称。listener
:一个回调函数,当主进程接收到来自渲染进程通过指定通道发送的同步请求时,这个回调函数会被执行。- 回调函数通常接受两个参数:
event
和args
。event
代表事件对象,可用于回复渲染进程等操作;args
是渲染进程发送的参数,可以是单个值或一组值,具体取决于渲染进程的发送方式。 - 回调函数需要返回一个值,这个值将作为响应发送回渲染进程。
- 回调函数通常接受两个参数:
示例:
实现需求:点击按钮从 D 盘读取 hello.txt 中的内容,并将结果呈现在页面上。
- 在页面中添加相关元素
html
<div id="content"></div>
<button id="btn">读取⽤户D盘的hello.txt</button
- 在
preload.js
中使⽤ipcRenderer.invoke('信道', 参数)
发送消息,与主进程通信。
typescript
// contextBridge:在隔离的上下文中创建一个安全的、双向的、同步的桥梁。
// ipcRenderer是渲染进程中用于与主进程进行通信的模块。
const {contextBridge, ipcRenderer} = require('electron')
// 把方法/数据暴露给渲染进程
contextBridge.exposeInMainWorld('customAPI', {
readFile: () => {
// invoke 的返回值永远是Promise
return ipcRenderer.invoke('read-file')
}
})
- 在
render.js
中,调用preload.js
暴露的saveFile
方法,通知主进程
typescript
const btn = document.getElementById('btn');
const content = document.getElementById('content');
btn.addEventListener('click', async () => {
let txt = await customAPI.readFile();
content.innerHTML = txt
});
- 主进程中,在加载页面之前,使用
ipcMain.handle('信道', 回调)
配置对应回调函数,接收
消息。
typescript
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path')
const fs = require('fs')
// 保存文件
function readFile(event,data) {
return fs.readFileSync('D:/hello.txt').toString()
}
function createWindow() {
const win = new BrowserWindow({
width: 1000, // 窗口宽度
height: 800, // 窗口高度
autoHideMenuBar: true, // 隐藏菜单栏
webPreferences: {
// 此处只能使用绝对路径
preload: path.join(__dirname, 'preload.js')
}
});
// 主进程注册对应回调
ipcMain.handle('read-file', readFile)
// 在窗口中加载一个远程页面
win.loadFile('./pages/index.html');
}
ipcRenderer.invoke(channel, ...args)
与 ipcRenderer.send(channel, ...args)
用于双向IPC的区别
ipcRenderer.send
需要设置第二个ipcRenderer.on
监听器来处理渲染器进程中的响应。 使用ipcRenderer.invoke
,直接获得作为 Promise 返回到原始 API 调用的响应值。- 没有显而易见的方法可以将
ipcRenderer.on('asynchronous-reply', (_event, arg) => {})
消息与原始的ipcRenderer.send('asynchronous-message')
消息配对。 如果不写备注,开发者可能也不知道asynchronous-reply
是asynchronous-message
通道的回复结果。如果通过这些通道非常频繁地来回传递消息,则需要添加其他应用代码来单独跟踪每个调用和响应。
模式3:主进程到渲染器进程
概述:主进程使用 win.webContents.send
发送消息,渲染进程使⽤ ipcRenderer.on
接收并处理消
息。
常用于:从主进程主动发送消息到渲染器进程。
示例:
需求:应⽤加载 6 秒钟后,主动给渲染进程发送⼀个消息
- 主进程中,使⽤
win.webContents.send('信道', 数据)
发送消息。
typescript
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 1000, // 窗口宽度
height: 800, // 窗口高度
autoHideMenuBar: true, // 隐藏菜单栏
webPreferences: {
// 此处只能使用绝对路径
preload: path.join(__dirname, 'preload.js')
}
});
// 在窗口中加载一个远程页面
win.loadFile('./pages/index.html');
// 创建⼀个定时器
setTimeout(() => {
win.webContents.send('message', 'hello!');
}, 6000);
}
preload.js
中使⽤ipcRenderer.on ('信道', 回调)
接收消息,并配置回调函数。
typescript
window.onload = () => {
customAPI.getMessage(logMessage);
};
function logMessage(event, str) {
console.log(event, str);
}