进程
有哪些进程

Electron 中主要控制两类进程:主进程、渲染器进程。
主进程
每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,它具有 require 模块和使用所有 Node.js API 的能力。
主进程的核心就是:使用 BrowserWindow 来创建和管理窗口。
渲染进程
每个 BrowserWindow 实例都对应一个单独的渲染器进程,运行在渲染器进程中的代码,必须遵守网页标准。 这也就意味着:渲染器进程无权直接访问 require 或使用任何 Node.js的API。
问题产生
处于渲染器进程的用户界面,该怎样才与 Node.js 和 Electron的原生桌面功能进行交互呢?
预加载脚本
预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限。可以访问 Node.js 的 API,同时又可以与网页内容进行安全的交互。简单说:它是 Node.js 和 Web API 的桥梁,Preload 脚本可以安全地将部分 Node.js 功能暴露给网页,从而减少安全风险。
进程 | 数量 | 环境 | 执行顺序 |
---|---|---|---|
主进程 | 1 | Node.js | 最先执行 |
预加载脚本 | 1 | 浏览器(可以访问部分 Node.js API) | 在渲染进程之前执行 |
渲染进程 | 多个 | 浏览器 | 最后执行 |
将预加载脚本与主进程关联
要想将预加载脚本(preload.js
)与主进程关联,需要在 main.js
中,通过 webPreferences
配置预加载脚本。
const path = require('node:path')
function createWindow() {
const win = new BrowserWindow({
width: 800, // 窗口宽度
height: 600, // 窗口高度
autoHideMenuBar: true, // 自动隐藏菜单栏
alwaysOnTop: true, // 置顶
webPreferences: {
preload: path.join(__dirname, './preload.js')
}
})
// 加载一个远程页面
win.loadURL('https://www.baidu.com/')
}
将预加载脚本与渲染进程关联
首先将预加载脚本与主进程关联起来,然后在预加载脚本中暴露数据给渲染进程。
const { contextBridge } = require('electron')
console.log('preload.js')
// 暴露数据给渲染进程
contextBridge.exposeInMainWorld('a', {
name: 'Jack',
age: 20,
version: process.version,
sayHello: () => {
return 'Hello, world!'
}
})
const btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
console.log(a)
})

进程间通信(IPC)
进程间通信 (IPC) 是在 Electron 中构建功能丰富的桌面应用程序的关键部分之一。 由于主进程和渲染器进程在 Electron 的进程模型具有不同的职责,因此 IPC 是执行许多常见任务的唯一方法,例如从 UI 调用原生 API 或从原生菜单触发 Web 内容的更改。
由于预加载脚本,无法使用全部的 Node.js API,比如:不能使用 fs
模块,但主进程是可以的,这时就需要进程通信了。简单说:要让 preload.js
通知 main.js
去调用 fs
模块干活。
关于 Electron
进程通信,我们要知道:
IPC
全称为:InterProcess Communication
,即:进程通信。IPC
是Electron
中最为核心的内容,它是从UI
调用原生API
的唯一方法!Electron
中,主要使用 ipcMain 和 ipcRenderer 来定义“通道”,进行进程通信。
渲染进程➡️主进程(单向)
要将单向 IPC 消息从渲染器进程发送到主进程,您可以使用 ipcRenderer.send API 发送消息,然后使用 ipcMain.on API接收。
常用于:在 Web 中调用主进程的 API,例如下面的这个需求:
点击按钮后,在用户的 D 盘创建一个hello.txt
文件,文件内容来自于用户输入。
<body>
<h1>Electron</h1>
<input class="ipt" type="text">
<button class="btn">点击我创建文件</button>
<script>
const btn = document.querySelector('.btn')
const ipt = document.querySelector('.ipt')
btn.addEventListener('click', () => {
// 调用预加载脚本暴露给的函数,并把 input 输入的内容传递给函数
myAPI.saveFile(ipt.value)
})
</script>
</body>
const { contextBridge, ipcRenderer } = require('electron')
// 暴露一个函数给渲染进程
contextBridge.exposeInMainWorld('myAPI', {
saveFile: (str) => {
// 给主进程发送一个消息,并携带渲染进程给的数据
ipcRenderer.send('create-file', str)
}
})
const path = require('node:path')
const fs = require('node:fs')
const { app, BrowserWindow, ipcMain } = require('electron')
// 用于创建窗口
function createWindow() {
const win = new BrowserWindow({
/** 其他配置项 */
webPreferences: {
preload: path.resolve(__dirname, './preload.js')
}
})
// 主进程注册对应回调
ipcMain.on('create-file', createFile)
// 加载一个本地页面
win.loadFile('pages/index.html')
}
// 用于创建文件
function createFile(event, data) {
fs.writeFileSync('D:/hello.txt', data)
}
// 当app准备好后,执行createWindow函数
app.on('ready', () => {
createWindow()
})
渲染进程↔主进程(双向)
概述:双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。
提示
- 渲染进程通过 ipcRenderer.invoke 发送消息
- 主进程使用 ipcMain.handle 接收并处理消息。
- 备注:
ipcRender.invoke
的返回值是Promise
实例。
常用于:从渲染器进程调用主进程方法并等待结果,例如下面的这个需求:
点击按钮从 D 盘读取 hello.txt
中的内容,并将结果呈现在页面控制台打印。
<button class="read">读取用户D盘的hello.txt</button>
<script>
const read = document.querySelector('.read')
read.addEventListener('click', async () => {
const data = await myAPI.readFile()
console.log(data)
})
</script>
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
readFile: () => {
return ipcRenderer.invoke('read-file')
}
})
// 用于创建窗口
function createWindow() {
// 主进程注册对应回调
ipcMain.handle('read-file', readFile)
// 加载一个本地页面
win.loadFile('pages/index.html')
}
// 读取文件
function readFile() {
return fs.readFileSync('D:/hello.txt').toString()
}
主进程到➡️渲染进程(单向)
概述:主进程使用 win.webContents.send 发送消息,渲染进程通过ipcRenderer.on 处理消息,
常用于:从主进程主动发送消息给渲染进程,例如下面的这个需求:
应用加载 6 秒钟后,主动给渲染进程发送一个消息,内容是:你好啊!
<script>
window.onload = () => {
myAPI.getMessage((event, str) => {
console.log(event, str)
})
}
</script>
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
getMessage: (callback) => {
return ipcRenderer.on('message', callback)
}
})
// 用于创建窗口
function createWindow() {
/**********/
// 加载一个本地页面
win.loadFile('pages/index.html')
// 创建一个定时器
setTimeout(() => {
win.webContents.send('message', '你好啊!')
}, 6000)
}