Skip to content

Electron 核心原理与实践

快速创建 Electron 项目

  • electron-forge
  • 原理:基于浏览器的架构

环境安装

  • Node.js: 22.x
  • pnpm i electron@latest --save-dev
  • 热更新工具:nodemon

基础示例

js
import { app, BrowserWindow } from 'electron'

// 创建一个窗口
const createWindow = () => {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // 相对于当前文件的路径
      preload: path.join(__dirname, 'preload.js'), // 加载预加载脚本
    },
  })
  mainWindow.loadFile('index.html')
  // 打开开发工具
  mainWindow.webContents.openDevTools()
}

// 创建另外一个新的窗口
const createSecondWindow = () => {
  const secondWindow = new BrowserWindow({
    width: 400,
    height: 300,
    parent: mainWindow, // 浮动在父窗口
  })
  secondWindow.loadFile('second.html')
}

app.on('ready', createWindow)

核心基础

  • 进程 app:控制主进程,控制应用程序的生命周期
  • 线程:渲染进程运行页面,主进程管理窗口和系统 API
  • 预加载脚本 (preload):在渲染进程加载之前执行,用于安全地暴露主进程 API

进程间通信 (IPC)

单向通信:渲染进程 → 主进程

js
// preload.js
const { contextBridge, ipcRenderer } = require('electron')

// 暴露版本信息到渲染进程
contextBridge.exposeInMainWorld('versions', {
  node: () => process.versions.node,
  chrome: () => process.versions.chrome,
  electron: () => process.versions.electron,
})

// 方式1:渲染进程发送消息到主进程
ipcRenderer.send('message', 'ping')
// 渲染进程监听主进程的回复
ipcRenderer.on('reply', (event, arg) => {
  console.log(arg) // 输出: 'pong'
})

// 方式2:通过 contextBridge 暴露方法
contextBridge.exposeInMainWorld('electron', {
  setIpcRenderer: (msg) => {
    ipcRenderer.send('message', msg)
  },
})
js
// 主进程接受渲染进程发送的消息
const { ipcMain } = require('electron')

ipcMain.on('message', (event, arg) => {
  console.log(arg) // 输出: 'ping'
  event.reply('reply', 'pong') // 回复渲染进程
})

单向通信:主进程 → 渲染进程

js
// 主进程给渲染进程发消息
const { ipcMain, ipcRenderer } = require('electron')
const win = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
  },
})
win.webContents.send('message', 'ping') // 发送消息到渲染进程

// 在 preload 渲染进程监听主进程的回复
ipcRenderer.on('message', (event, arg) => {
  console.log(arg) // 输出: 'pong'
})

双向通信:invoke / handle

js
// 渲染进程给主进程发送消息:invoke 发送消息并等待主进程回复
ipcRenderer.invoke('message', 'ping').then((response) => {
  console.log(response) // 输出: 'pong'
})

// 主进程接受渲染进程发送的消息: handle 响应
ipcMain.handle('message', (event, arg) => {
  console.log(arg) // 输出: 'ping'
  return 'pong' // 回复渲染进程
})

沙箱与安全

  • 渲染进程是一个独立的环境,不能直接访问主进程的变量和函数
  • 渲染进程只能通过预加载脚本(preload.js)来与主进程通信
  • 配置 nodeIntegration: true 可以让渲染进程直接访问 Node.js API(不推荐)
js
// 主进程
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true, // 开启 nodeIntegration(不推荐)
    },
  })
  mainWindow.loadFile('index.html')
}

remote 模块

  • 渲染进程可以通过 remote 模块来访问主进程的变量和函数
  • 比如渲染进程可以通过 remote 模块来访问主进程的 BrowserWindow 类
js
// 渲染进程
const { remote } = require('electron')
const BrowserWindow = remote.BrowserWindow

创建多个窗口

js
// 主进程
const { app, BrowserWindow } = require('electron')

const createWindow = () => {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  })
  mainWindow.loadFile('index.html')
}

// 创建另外一个新的窗口
const createSecondWindow = () => {
  const secondWindow = new BrowserWindow({
    width: 400,
    height: 300,
    parent: mainWindow, // 浮动在父窗口,不加的时候是一个互不相干的窗口
    modal: true, // 模态窗口,阻塞父窗口
    frame: false, // 无边框窗口
  })
  secondWindow.loadFile('second.html')
}

app.on('ready', createWindow)

// 当所有窗口都关闭时退出应用
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})