进程通信

由于 preload.js 并无法使用全部 Node 的API ,比如:不能使用 Node 中的 fs 模块,但主进程(main.js)是可以的,这时就需要进程通信了。简单说:要让 preload.js 通知 main.js 去调用 fs 模块。

关于Electron进程通信,我们要知道:

  • IPC 全称为:InterProcess Communication,即:进程通信。

  • IPCElectron 中最为核心的内容,它是从 UI 调用原生 API 的唯一方法!

  • Electron 中,主要使用 ipcMainipcRenderer 来定义“通道”,进行进程通信。

渲染进程=>主进程(单向)

在渲染器进程中 ipcRenderer.send 发送消息,在主进程中使用 ipcMain.on 接收消息。

常用于:在 Web 中调用主进程的 API

(1)页面中添加相关元素,renderer.js 中添加对应脚本

1<!DOCTYPE html>
2<html lang="en">
3	<head>
4		<meta charset="UTF-8" />
5		<meta
6			httpEquiv="Content-Security-Policy"
7			content="default-src 'self'; script-src 'self'"
8		/>
9		<title>Hello World!</title>
10	</head>
11
12	<body>
13		<input
14			id="content"
15			type="text"
16		/>
17		<button id="btn">在用户的桌面创建一个hello.txt</button>
18		<script
19			type="text/javascript"
20			src="./renderer.js"
21		></script>
22	</body>
23</html>
1const btn = document.getElementById("btn")
2const content = document.getElementById("content")
3btn.addEventListener("click", () => {
4	console.log(content.value)
5	myAPI.saveFile(content.value)
6})

:::

(2)preload.js 中使用 ipcRenderer.send('信道',参数) 发送消息,与主进程通信。

1const { contextBridge, ipcRenderer } = require("electron")
2
3// 暴露数据给渲染进程
4contextBridge.exposeInMainWorld("myAPI", {
5	name: "dancy",
6	age: 28,
7	saveFile(str) {
8		// 渲染进程给主进程发送一个消息
9		ipcRenderer.send("create-file", str)
10	},
11})

(3)主进程中,在加载页面之前,使用 ipcMain.on('信道',回调) 配置对应回调函数,接收消息。

1const { app, BrowserWindow, ipcMain } = require("electron")
2const fs = require("node:fs")
3const path = require("node:path")
4
5// 获取桌面路径
6const desktopPath = path.join(process.env.HOME, "Desktop")
7// 要写入的文件名
8const fileName = "hello.txt"
9// 使用writeFileSync方法创建并写入文件
10function createFile(event, data) {
11	fs.writeFileSync(path.join(desktopPath, fileName), data)
12}
13
14const createWindow = () => {
15	const win = new BrowserWindow({
16		width: 300,
17		height: 200,
18		autoHideMenuBar: true,
19		webPreferences: {
20			preload: path.resolve(__dirname, "./preload.js"),
21		},
22	})
23
24	// 主进程注册对应回调
25	ipcMain.on("create-file", createFile)
26
27	win.loadFile("./pages/index.html")
28}
29
30app.on("ready", () => {
31	createWindow()
32	app.on("activate", () => {
33		if (BrowserWindow.getAllWindows().length === 0) createWindow()
34	})
35})
36
37app.on("window-all-closed", () => {
38	if (process.platform !== "darwin") app.quit()
39})

渲染进程<=>主进程(双向)

渲染进程通过 ipcRenderer.invoke 发送消息,主进程使用 ipcMain.handle 接收并处理消息。

备注:ipcRender.invoke 的返回值是 Promise 实例。

常用于:从渲染器进程调用主进程方法并等待结果

(1)页面中添加相关元素,renderer.js中添加对应脚本

1<!DOCTYPE html>
2<html lang="en">
3	<head>
4		<meta charset="UTF-8" />
5		<meta
6			httpEquiv="Content-Security-Policy"
7			content="default-src 'self'; script-src 'self'"
8		/>
9		<title>Hello World!</title>
10	</head>
11
12	<body>
13		<button id="btn">读取用户桌面hello.txt的内容</button>
14		<script
15			type="text/javascript"
16			src="./renderer.js"
17		></script>
18	</body>
19</html>
1const btn = document.getElementById("btn")
2btn.addEventListener("click", async () => {
3	const data = await myAPI.readFile()
4	document.body.innerHTML += `<h2>${data}</h2>`
5})

:::

(2)preload.js中使用 ipcRenderer.invoke('信道',参数) 发送消息,与主进程通信。

1const { contextBridge, ipcRenderer } = require("electron")
2
3// 暴露数据给渲染进程
4contextBridge.exposeInMainWorld("myAPI", {
5	name: "dancy",
6	age: 28,
7	saveFile(str) {
8		// 渲染进程给主进程发送一个消息
9		ipcRenderer.send("create-file", str)
10	},
11	readFile() {
12		return ipcRenderer.invoke("read-file")
13	},
14})

(3)主进程中,在加载页面之前,使用 ipcMain.handle('信道',回调) 接收消息,并配置回调函数。

1const { app, BrowserWindow, ipcMain } = require("electron")
2const fs = require("node:fs")
3const path = require("node:path")
4
5// 获取桌面路径
6const desktopPath = path.join(process.env.HOME, "Desktop")
7// 要写入的文件名
8const fileName = "hello.txt"
9// 使用writeFileSync方法创建并写入文件
10function createFile(event, data) {
11	fs.writeFileSync(path.join(desktopPath, fileName), data)
12}
13//读取文件
14function readFile() {
15	return fs.readFileSync(path.join(desktopPath, fileName)).toString()
16}
17
18const createWindow = () => {
19	const win = new BrowserWindow({
20		width: 300,
21		height: 200,
22		autoHideMenuBar: true,
23		webPreferences: {
24			preload: path.resolve(__dirname, "./preload.js"),
25		},
26	})
27
28	ipcMain.on("create-file", createFile)
29
30	// 主进程注册对应回调
31	ipcMain.handle("read-file", readFile)
32
33	win.loadFile("./pages/index.html")
34}
35
36app.on("ready", () => {
37	createWindow()
38	app.on("activate", () => {
39		if (BrowserWindow.getAllWindows().length === 0) createWindow()
40	})
41})
42
43app.on("window-all-closed", () => {
44	if (process.platform !== "darwin") app.quit()
45})

主进程到=>渲染进程

概述:主进程使用 win.webContents.send 发送消息,渲染进程通过 ipcRenderer.on 处理消息

常用于: 从主进程主动发消息给渲染进程

(1)页面中添加相关元素, renderer.js 中添加对应脚本

1window.onload = () => {
2	myAPI.getMessage(logMessage)
3}
4function logMessage(event, str) {
5	console.log(event, str)
6	document.body.innerHTML = str
7}

(2)preload.js中使用 ipcRenderer.on ('信道',回调) 接收消息,并配置回调函数。

1const { contextBridge, ipcRenderer } = require("electron")
2
3// 暴露数据给渲染进程
4contextBridge.exposeInMainWorld("myAPI", {
5	name: "dancy",
6	age: 28,
7	saveFile(str) {
8		// 渲染进程给主进程发送一个消息
9		ipcRenderer.send("create-file", str)
10	},
11	readFile() {
12		return ipcRenderer.invoke("read-file")
13	},
14	getMessage: (callback) => {
15		return ipcRenderer.on("message", callback)
16	},
17})

(3)主进程中,在合适的时候,使用 win.webContents.send('信道',数据)发送消息。

1const { app, BrowserWindow, ipcMain } = require("electron")
2const fs = require("node:fs")
3const path = require("node:path")
4
5// 获取桌面路径
6const desktopPath = path.join(process.env.HOME, "Desktop")
7// 要写入的文件名
8const fileName = "hello.txt"
9// 使用writeFileSync方法创建并写入文件
10function createFile(event, data) {
11	fs.writeFileSync(path.join(desktopPath, fileName), data)
12}
13//读取文件
14function readFile() {
15	return fs.readFileSync(path.join(desktopPath, fileName)).toString()
16}
17
18// 创建窗口
19const createWindow = () => {
20	const win = new BrowserWindow({
21		width: 300,
22		height: 200,
23		autoHideMenuBar: true,
24		webPreferences: {
25			preload: path.resolve(__dirname, "./preload.js"),
26		},
27	})
28
29	// 主进程注册对应回调
30	ipcMain.on("create-file", createFile)
31
32	// 主进程注册对应回调
33	ipcMain.handle("read-file", readFile)
34
35	// 加载指定的文件到新创建的窗口中,这个文件通常是应用的主页面。
36	win.loadFile("./pages/index.html")
37
38	// 创建一个定时器
39	setTimeout(() => {
40		win.webContents.send("message", "你好啊!")
41	}, 2000)
42}
43
44app.on("ready", () => {
45	createWindow()
46	app.on("activate", () => {
47		if (BrowserWindow.getAllWindows().length === 0) createWindow()
48	})
49})
50
51
52app.on("window-all-closed", () => {
53	if (process.platform !== "darwin") app.quit()
54})