Electron 核心架构
主进程、渲染进程与 IPC 通信机制
目录
进程模型
两类进程
┌─────────────────────────────────────────────────────────────┐
│ Electron 应用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ │
│ │ Main Process │ ← 唯一,Node.js 环境 │
│ │ │ │
│ │ • 应用生命周期管理 │ │
│ │ • 创建 BrowserWindow │ │
│ │ • 系统 API 调用 │ │
│ │ • 原生菜单/托盘 │ │
│ └──────────┬──────────┘ │
│ │ IPC │
│ ┌──────────┴──────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │Renderer Process│ │Renderer Process│ ← 多个,Chromium 环境 │
│ │ (Window 1) │ │ (Window 2) │ │
│ │ │ │ │ │
│ │ HTML/CSS/JS │ │ HTML/CSS/JS │ │
│ │ 运行 Web 页面 │ │ 运行 Web 页面 │ │
│ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘进程职责对比
| 特性 | Main Process | Renderer Process |
|---|---|---|
| 数量 | 1 个 | 多个 |
| 环境 | Node.js | Chromium |
| 访问能力 | 文件系统、系统 API | DOM、Web API |
| 入口文件 | main.js | index.html |
IPC 通信
渲染进程 → 主进程 (单向)
typescript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
sendMessage: (message) => ipcRenderer.send('message-channel', message)
});
// main.js
const { ipcMain } = require('electron');
ipcMain.on('message-channel', (event, message) => {
console.log('Received:', message);
});
// renderer.js
window.electronAPI.sendMessage('Hello from renderer');渲染进程 → 主进程 (双向)
typescript
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
invoke: (channel, data) => ipcRenderer.invoke(channel, data)
});
// main.js
ipcMain.handle('get-user-data', async (event, userId) => {
const user = await database.getUser(userId);
return user;
});
// renderer.js
const user = await window.electronAPI.invoke('get-user-data', 123);
console.log(user);主进程 → 渲染进程
typescript
// main.js
const win = new BrowserWindow({ /* ... */ });
// 发送到特定窗口
win.webContents.send('update-available', { version: '2.0.0' });
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
onUpdateAvailable: (callback) => {
ipcRenderer.on('update-available', (event, data) => callback(data));
}
});
// renderer.js
window.electronAPI.onUpdateAvailable((data) => {
console.log('New version:', data.version);
});渲染进程 ↔ 渲染进程
typescript
// 通过主进程中转
// main.js
ipcMain.on('message-to-window', (event, { windowId, message }) => {
const targetWindow = BrowserWindow.fromId(windowId);
targetWindow?.webContents.send('message-from-window', message);
});预加载脚本
为什么需要 Preload?
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Main │ │ Preload │ │ Renderer │
│ (Node.js) │────▶│ (桥接层) │────▶│ (Web) │
│ │ │ │ │ │
│ 有完整权限 │ │ 受限 Node │ │ 无 Node │
└─────────────┘ └─────────────┘ └─────────────┘安全的 Preload 实现
typescript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// ✅ 只暴露必要的 API
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作
readFile: (path) => ipcRenderer.invoke('fs:readFile', path),
writeFile: (path, data) => ipcRenderer.invoke('fs:writeFile', path, data),
// 系统信息
getPlatform: () => process.platform,
// 事件监听(带清理)
onThemeChange: (callback) => {
const handler = (event, theme) => callback(theme);
ipcRenderer.on('theme-changed', handler);
return () => ipcRenderer.removeListener('theme-changed', handler);
}
});
// ❌ 危险:暴露整个 ipcRenderer
contextBridge.exposeInMainWorld('ipc', ipcRenderer); // 不要这样做主进程验证
typescript
// main.js
ipcMain.handle('fs:readFile', async (event, filePath) => {
// 验证路径是否在允许范围内
const allowedDir = app.getPath('userData');
const resolvedPath = path.resolve(filePath);
if (!resolvedPath.startsWith(allowedDir)) {
throw new Error('Access denied');
}
return fs.readFileSync(resolvedPath, 'utf-8');
});BrowserWindow
创建窗口
typescript
const { BrowserWindow, app } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
// 外观
frame: true, // 是否显示边框
titleBarStyle: 'hiddenInset', // macOS 标题栏样式
transparent: false,
backgroundColor: '#ffffff',
// 安全配置
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // 上下文隔离
nodeIntegration: false, // 禁用 Node
sandbox: true // 沙箱模式
}
});
// 开发环境加载 localhost
if (process.env.NODE_ENV === 'development') {
win.loadURL('http://localhost:3000');
win.webContents.openDevTools();
} else {
win.loadFile('dist/index.html');
}
return win;
}
app.whenReady().then(createWindow);窗口事件
typescript
win.on('close', (e) => {
e.preventDefault();
// 确认对话框
dialog.showMessageBox(win, {
type: 'question',
buttons: ['Yes', 'No'],
message: 'Are you sure you want to quit?'
}).then(({ response }) => {
if (response === 0) {
win.destroy();
}
});
});
win.on('maximize', () => {
win.webContents.send('window-maximized');
});
win.on('unmaximize', () => {
win.webContents.send('window-unmaximized');
});多窗口管理
typescript
const windows = new Map();
function createWindow(name, options = {}) {
const win = new BrowserWindow({
...defaultOptions,
...options
});
windows.set(name, win);
win.on('closed', () => {
windows.delete(name);
});
return win;
}
function getWindow(name) {
return windows.get(name);
}应用生命周期
typescript
const { app } = require('electron');
// 所有窗口关闭时
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// macOS 点击 dock 图标
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// 应用即将退出
app.on('before-quit', () => {
// 保存状态等
});
// 阻止多实例
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, argv, workingDir) => {
// 聚焦已有窗口
const mainWindow = getWindow('main');
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
}高频面试题
Q1: 主进程和渲染进程的区别?
| 特性 | 主进程 | 渲染进程 |
|---|---|---|
| 入口 | main.js | HTML 文件 |
| 数量 | 1 个 | 多个 |
| 能力 | Node.js + Electron API | DOM + Web API |
| 用途 | 应用控制、系统交互 | UI 展示 |
Q2: 为什么需要 Context Isolation?
安全隔离:防止渲染进程中的恶意代码访问 Node.js API。
typescript
// 开启 contextIsolation 后
// preload 的变量不会泄露到 renderer
// 只有 contextBridge 暴露的 API 可用Q3: IPC 通信有哪几种方式?
| 方式 | 方向 | 用途 |
|---|---|---|
ipcRenderer.send + ipcMain.on | Renderer → Main | 单向通知 |
ipcRenderer.invoke + ipcMain.handle | Renderer ↔ Main | 异步调用 |
webContents.send | Main → Renderer | 主动推送 |
Q4: 如何在渲染进程间通信?
- 通过主进程中转:推荐方式
- MessagePort:高性能场景
- 共享存储:localStorage/IndexedDB(同源限制)
项目结构
my-electron-app/
├── package.json
├── main/
│ ├── main.js # 主进程入口
│ ├── preload.js # 预加载脚本
│ └── ipc/
│ └── handlers.js # IPC 处理器
├── renderer/
│ ├── index.html
│ ├── src/
│ │ ├── App.tsx
│ │ └── ...
│ └── public/
│ └── ...
└── resources/
└── icons/