上一篇文章讲了electron
项目的创建和打包,本篇就简单讲讲它的使用
项目目录结构

src/main/index.ts
完整代码:
javascript
import { app, shell, BrowserWindow } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
function createWindow(): void {
// createWindow() 函数将您的页面加载到新的 BrowserWindow 实例中
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
//变量 ELECTRON_RENDERER_URL 是 Vite 开发服务运行的本地 URL
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
//开发环境
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
//生产环境 文件路径
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
//窗口应用的生命周期
//应用准备就绪时
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.electron')
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
//注意:窗口无法在 ready 事件前创建
//加载新的窗口
createWindow()
app.on('activate', function () {
//如果没有窗口打开则打开一个窗口 (macOS)
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
//关闭所有窗口时退出应用 (Windows & Linux)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
简单来讲:就是创建一个指定宽高的窗口mainWindow
, 根据当前环境变量
来确定窗口浏览器是加载本地html还是本地URL(mainWindow.loadURL/loadFile
),然后在应用的生命周期函数中管理
该窗口
如果是多页,可以这样使用
arduino
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/view.html`)
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/view.html'))
}
主进程与渲染进程
主线程 :Electron程序会在运行package.json
文件的main
字段设置的入口文件,这个入口文件控制着Electron的主线程
在主进程中可以使用Node.js
实例,负责应用的生命周期、展示原生窗口、执行特殊操作和管理渲染进程。
渲染器进程:负责展示图形内容,可以将渲染的网页指向web地址或本地HTML文件。渲染器和常规的网页行为很相似,访问的web API也相同。(本项目中react代码可以认为是渲染器进程)
两者关系图

一个主进程有多个渲染进程
一个 Electron 应用有且只有一个主进程。并且创建窗口等所有系统事件都要在主进程中进行。
主进程与渲染进程通信需要用到进程间通信模组IPC
,想要在渲染进程中使用Node.js
等API需要使用预加载脚本
安全地将特权API暴露至渲染进程。
预加载脚本
Electron的主进程是一个拥有着完全操作系统访问权限的Node.js环境,除了Electron模组之外也可以访问Node.js
内置模块和所有通过npm
安装的包。
出于安全原因,渲染进程默认跑在网页页面上而非Node.js中 。 为了将Electron的不同类型的进程桥接在一起,需要使用被称为预加载
的特殊脚本
上篇文章使用electron-vite
工具构建的electron项目,已经为我们集中配置了主进程、渲染和预加载脚本Vite配置

preload/index.ts
javascript
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// 自定义 APIs 给渲染进程
const api = {}
//使用' contextBridge ' api将Electron api暴露给渲染进程
// 默认开启了上下文隔离
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}
preload/index.d.ts
typescript
import { ElectronAPI } from '@electron-toolkit/preload'
declare global {
interface Window {
electron: ElectronAPI
api: unknown
}
}
现在渲染进程中通过使用window.electron
就可以访问到主线程中才有的API了,比如当前版本信息


进程间通信的几种方式
在 Electron 中,进程使用 ipcMain
和 ipcRenderer
模块,通过开发人员定义的"通道"传递消息来进行通信。 这些通道是 任意 可以随意命名它们 单向 双向
渲染进程 ➡️ 主线程 ipcMain.on ipcRenderer.send
例:渲染进程向主线程发消息,修改应用标题
main/index.ts
scss
//应用准备就绪时
app.whenReady().then(() => {
//主进程监听渲染进程发过来的消息
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
})
//加载新的窗口
createWindow()
...
})
preload/index.ts
javascript
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// 自定义 APIs 给渲染进程
const api = {}
//使用' contextBridge ' api将Electron api暴露给渲染进程
// 默认开启了上下文隔离
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', {
//将单向 IPC 消息从渲染器进程发送到主进程
setTitle: (val) => ipcRenderer.send('set-title', val)
})
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}
preload/index.d.ts
需要在此文件中声明setTitle方法,不然会报ts问题,下面例代码就不做解释了
typescript
declare global {
interface Window {
electron: ElectronAPI
api: {
setTitle: (value: string) => void
}
}
}
渲染页面renderer/src/components
css
<button onClick={(): void => window.api.setTitle('嘿 你真帅')}>更新标题</button>
效果

渲染进程 ↔️ 主线程 ipcMain.handle ipcRenderer.invoke
渲染器进程代码调用主进程模块并等待结果
例:渲染进程需要获取当前硬件的MAC地址
main/index.ts
javascript
import os from 'os'
function handleNetwork(): string {
let mac = ''
const networkInterfaces = os.networkInterfaces()
for (const i in networkInterfaces) {
for (const j in networkInterfaces[i]) {
if (
networkInterfaces[i][j]['family'] === 'IPv4' &&
networkInterfaces[i][j]['mac'] !== '00:00:00:00:00:00' &&
networkInterfaces[i][j]['address'] !== '127.0.0.1'
) {
mac = networkInterfaces[i][j]['mac']
}
}
}
return mac
}
app.whenReady().then(() => {
//双向通信
ipcMain.handle('get-network', handleNetwork)
})
preload/index.ts
arduino
contextBridge.exposeInMainWorld('api', {
//暴露了一个`get-network` 函数,它调用并返回
getNetwork: () => ipcRenderer.invoke('get-network')
})
渲染页面renderer/src/components
javascript
window.api.getNetwork().then((value) => console.log('value', value)) //value 9c:3e:53:93:55:11
渲染进程 ⬅️ 主进程 WebContents
将消息从主进程发送到渲染器进程时,需要指定是哪一个渲染器接收消息 例:主进程在接到某消息后要将该消息发送给渲染进程
main/index.ts
javascript
//主进程发送给mainWindow这个渲染进程,修改背景颜色
//mainWindow(窗口名)
setTimeout(() => {
mainWindow.webContents.send('change-color', 'pink')
}, 2000)
preload/index.ts
javascript
contextBridge.exposeInMainWorld('api', {
//监听
onChangeColor: (callback) => ipcRenderer.on('change-color', (e, value) => callback(value))
})
渲染页面renderer/src/components
scss
useEffect(() => {
//监听主进程发送的消息
window.api.onChangeColor((value: string) => {
setColor(value)
})
}, [])
渲染进程 ➡️ 渲染进程
1、将主进程作为渲染器之间的消息代理
2、使用MessagePort
✨✨✨ 如有不正确的地方,欢迎指出哈~