主进程与渲染进程
原创2026/9/9...大约 6 分钟
主进程与渲染进程 🛠️
理解 Electron 的进程模型是掌握 Electron 开发的核心。Electron 应用启动后,会同时存在 多个进程,每个进程各司其职,相互协作。
进程架构 💎
一个 Electron 应用主要由以下三类进程组成:
| 进程类型 | 数量 | 运行环境 | 主要职责 |
|---|---|---|---|
| 主进程 | 1 个 | Node.js | 应用生命周期、窗口管理、原生 API 调用 |
| 预加载脚本 | 多个 | Node.js + Chromium | 在渲染进程加载前运行,桥接主进程与渲染进程 |
| 渲染进程 | 多个 | Chromium | 展示 Web 页面,处理用户交互 |
┌────────────────────────────────────────────────────────┐
│ Electron 应用 │
├────────────────────────────────────────────────────────┤
│ 主进程 (Main Process) │
│ ┌──────────────────────────────────────────────┐ │
│ │ - 创建 / 管理 BrowserWindow │ │
│ │ - 系统菜单、托盘、快捷键 │ │
│ │ - 调用原生 API(dialog、fs、shell 等) │ │
│ │ - 应用生命周期管理(ready、quit 等) │ │
│ └──────────────────────────────────────────────┘ │
│ ↕ IPC 通信 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 预加载脚本 1 │ │ 预加载脚本 2 │ │ 预加载脚本 3 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ↓ ↓ ↓ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 渲染进程 1 │ │ 渲染进程 2 │ │ 渲染进程 3 │ │
│ │ (Web 页面) │ │ (Web 页面) │ │ (Web 页面) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────┘关键概念
- 主进程只有一个:每个 Electron 应用只有一个主进程,负责管理整个应用
- 渲染进程有多个:每创建一个
BrowserWindow,就会产生一个独立的渲染进程 - 预加载脚本一对一:每个 BrowserWindow 都会加载自己的 preload.js
主进程 💎
主进程是 Electron 应用的"控制中心",拥有完整的 Node.js 能力,可以通过 require 引入所有 Node.js 模块。
主进程入口文件
// src/main/index.ts
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron'
import { join } from 'path'
// 注意:主进程中可以使用所有 Node.js API
const fs = require('fs')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 1024,
height: 768,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
},
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
// 主进程独有的能力:注册 IPC 监听
ipcMain.on('message-from-renderer', (_, msg) => {
console.log('主进程收到渲染进程消息:', msg)
})
// 主进程独有的能力:调用原生对话框
ipcMain.handle('open-file-dialog', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
})
return result.filePaths
})
})主进程能做什么?
- ✅ 创建和管理
BrowserWindow - ✅ 调用所有 Node.js API(fs、path、http 等)
- ✅ 调用 Electron 提供的原生 API(dialog、shell、Menu、Tray 等)
- ✅ 注册全局快捷键、菜单、托盘
- ✅ 监听应用生命周期事件
- ❌ 不能直接操作 DOM(没有 window、document)
渲染进程 💎
渲染进程就是我们熟悉的 Web 页面,运行在 Chromium 内核中,没有直接访问 Node.js API 的能力(出于安全考虑)。
// src/renderer/src/main.ts
// 渲染进程:典型的 Web 前端代码
document.getElementById('btn')?.addEventListener('click', () => {
console.log('当前是渲染进程')
})
// ❌ 错误:渲染进程中无法直接 require Node.js 模块
// const fs = require('fs')
// ✅ 正确:通过 preload 暴露的 API 访问
;(window as any).electron.sendMessage('Hello')渲染进程能做什么?
- ✅ 完整的 Web API(DOM、BOM、Fetch、Canvas 等)
- ✅ 渲染 HTML/CSS/JavaScript
- ✅ 通过
window.electron.xxx调用主进程暴露的 API - ❌ 不能直接访问 Node.js API
- ❌ 不能调用原生 API
预加载脚本 💎
预加载脚本运行在同时具备 Node.js 能力与 Web 能力的特殊环境中。它在渲染进程加载页面之前执行,是连接主进程与渲染进程的"桥梁"。
// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron'
// 通过 contextBridge 暴露 API 到 window 对象
contextBridge.exposeInMainWorld('electron', {
// 主动向主进程发消息
sendMessage: (msg: string) => ipcRenderer.send('message', msg),
// 监听主进程发来的消息
onMessage: (callback: (msg: string) => void) => {
ipcRenderer.on('reply', (_, msg) => callback(msg))
},
// 调用主进程的 handle 方法(双向通信)
openFile: () => ipcRenderer.invoke('open-file-dialog'),
})渲染进程中使用:
// src/renderer/src/main.ts
const sendBtn = document.getElementById('send-btn')
const openFileBtn = document.getElementById('open-file-btn')
sendBtn?.addEventListener('click', () => {
;(window as any).electron.sendMessage('Hello Main Process')
})
openFileBtn?.addEventListener('click', async () => {
const filePath = await (window as any).electron.openFile()
console.log('选中的文件:', filePath)
})进程的创建与销毁 💎
主进程的生命周期 👻
import { app, BrowserWindow } from 'electron'
// 应用启动完成(仅触发一次)
app.whenReady().then(() => {
console.log('应用就绪')
createWindow()
})
// 所有窗口关闭(Windows / Linux 上应用会退出)
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', () => {
console.log('即将退出')
})渲染进程的创建 👻
import { BrowserWindow } from 'electron'
// 创建一个渲染进程窗口
const win = new BrowserWindow({
width: 1024,
height: 768,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
})
// 销毁窗口(触发 closed 事件)
win.close()
// 立即销毁(不会触发 close 事件)
win.destroy()进程间通信(IPC)💎
主进程与渲染进程之间无法直接共享变量,必须通过 IPC(Inter-Process Communication) 通信。详细用法请参考下一章。
| 通信方式 | 方向 | 适用场景 |
|---|---|---|
ipcRenderer.send + ipcMain.on | 渲染 → 主(单向) | 通知主进程执行操作 |
ipcRenderer.invoke + ipcMain.handle | 渲染 → 主(双向) | 渲染进程需要主进程返回数据 |
webContents.send + ipcRenderer.on | 主 → 渲染(单向) | 主进程主动通知渲染进程 |
注意事项
- 不要在渲染进程中开启
nodeIntegration: true:会让渲染进程拥有完整 Node.js 能力,极度危险 - 始终开启
contextIsolation: true:使用contextBridge安全地暴露 API - 预加载脚本中只暴露必要的 API:避免一次性把
ipcRenderer整个暴露出去
完整示例 💎
下面是一个完整的"主进程 + 预加载 + 渲染进程"示例,演示三者的协作关系:
主进程 (main.ts)
import { app, BrowserWindow, ipcMain, dialog } from 'electron'
import { join } from 'path'
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
// 注册 IPC 处理器
ipcMain.handle('pick-file', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
})
return result.filePaths[0]
})
ipcMain.handle('read-file', async (_, filePath: string) => {
const fs = await import('fs/promises')
return fs.readFile(filePath, 'utf-8')
})
createWindow()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})预加载脚本 (preload.ts)
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('api', {
pickFile: () => ipcRenderer.invoke('pick-file'),
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),
})渲染进程 (renderer.ts)
// 注意:渲染进程无法直接 require Node.js 模块
const fileBtn = document.getElementById('pick-btn')
const content = document.getElementById('content')
fileBtn?.addEventListener('click', async () => {
// 通过 window.api 调用主进程能力
const filePath = await (window as any).api.pickFile()
console.log('选中文件:', filePath)
if (filePath) {
const text = await (window as any).api.readFile(filePath)
if (content) content.textContent = text
}
})渲染页面 (index.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>IPC 示例</title>
</head>
<body>
<button id="pick-btn">选择文件并读取</button>
<pre id="content"></pre>
</body>
</html>小结
本章我们学习了 Electron 的核心架构:
- 主进程:应用的"大脑",负责窗口管理、生命周期、原生 API
- 预加载脚本:主进程与渲染进程的"桥梁",通过
contextBridge暴露安全 API - 渲染进程:实际显示的 Web 页面,运行在 Chromium 中
下一章我们将详细学习 进程间通信(IPC) 的所有用法。
至此,本章节的学习就到此结束了,如有疑惑,可对接技术客服进行相关咨询。