Electron 中级教程:从基础功能到实用应用开发
在掌握了 Electron 的基础架构和核心用法后,我们需要进一步学习如何将零散的功能组合成实用的桌面应用。本文将聚焦中级开发场景,涵盖窗口管理、数据持久化、原生对话框集成、全局快捷键以及调试技巧,帮助你构建功能更完善、体验更流畅的应用。
一、窗口管理进阶:多窗口与窗口交互
真实应用往往需要多个窗口(如主窗口、设置窗口、关于窗口),Electron 提供了灵活的窗口管理能力,包括窗口间通信、模态窗口和窗口状态保存。
1. 多窗口创建与通信
创建多窗口的核心是通过 BrowserWindow 实例化多个窗口,并通过 IPC 实现数据传递。
示例:创建设置窗口
javascript
运行
// main.js 中新增创建设置窗口的函数
let settingsWindow = null;
function createSettingsWindow() {
// 避免重复创建窗口
if (settingsWindow) {
settingsWindow.focus();
return;
}
settingsWindow = new BrowserWindow({
width: 400,
height: 300,
title: '设置',
parent: mainWindow, // 指定父窗口(关闭父窗口时子窗口也会关闭)
modal: false, // 非模态窗口(可与其他窗口交互)
webPreferences: {
contextIsolation: false,
nodeIntegration: true
}
});
// 加载设置页面
settingsWindow.loadFile('settings.html');
// 窗口关闭时清理引用
settingsWindow.on('closed', () => {
settingsWindow = null;
});
}
// 通过菜单或快捷键触发
menuTemplate.push({
label: '设置',
click: () => createSettingsWindow()
});
窗口间通信:通过主进程转发实现多窗口数据同步
javascript
运行
// 主进程:监听设置窗口的配置变更
ipcMain.on('settings-updated', (event, newSettings) => {
// 保存配置(后续数据持久化会讲到)
saveSettings(newSettings);
// 通知主窗口更新配置
mainWindow.webContents.send('settings-changed', newSettings);
});
// 主窗口(渲染进程):接收配置变更
ipcRenderer.on('settings-changed', (event, settings) => {
console.log('配置已更新:', settings);
// 更新界面显示
});
2. 模态窗口与对话框
模态窗口(Modal Window)会阻塞父窗口的交互,适合需要用户必须处理的场景(如确认对话框)。
javascript
运行
// 创建模态窗口
function createConfirmWindow(message) {
return new Promise((resolve) => {
const confirmWindow = new BrowserWindow({
width: 300,
height: 200,
parent: mainWindow,
modal: true, // 启用模态
show: false, // 先不显示
webPreferences: {
contextIsolation: false,
nodeIntegration: true
}
});
confirmWindow.loadFile('confirm.html');
// 窗口加载完成后显示
confirmWindow.on('ready-to-show', () => {
confirmWindow.show();
// 向模态窗口发送消息
confirmWindow.webContents.send('set-message', message);
});
// 监听模态窗口的确认/取消事件
ipcMain.once('confirm-result', (event, result) => {
resolve(result);
confirmWindow.close();
});
});
}
// 使用示例
async function deleteItem() {
const result = await createConfirmWindow('确定要删除吗?');
if (result) {
console.log('用户确认删除');
}
}
3. 窗口状态保存与恢复
用户关闭窗口后,再次打开时希望保留上次的大小和位置,可通过 electron-window-state 实现:
bash
运行
npm install electron-window-state --save
javascript
运行
const windowStateKeeper = require('electron-window-state');
// 在 createWindow 函数中使用
function createWindow() {
// 加载上次保存的窗口状态
const mainWindowState = windowStateKeeper({
defaultWidth: 800,
defaultHeight: 600
});
const mainWindow = new BrowserWindow({
x: mainWindowState.x, // 上次的x坐标
y: mainWindowState.y, // 上次的y坐标
width: mainWindowState.width,
height: mainWindowState.height,
// 其他配置...
});
// 监听窗口状态变化并保存
mainWindowState.manage(mainWindow);
}
二、数据持久化:本地存储方案
桌面应用需要保存用户配置、缓存数据等,Electron 支持多种本地存储方案,选择合适的方案能提升开发效率。
1. 轻量配置:electron-store
对于简单的键值对配置(如用户偏好设置),electron-store 是最佳选择:
bash
运行
npm install electron-store --save
javascript
运行
// 主进程或渲染进程中使用
const Store = require('electron-store');
const store = new Store({
defaults: { // 默认配置
theme: 'light',
windowSize: { width: 800, height: 600 }
}
});
// 保存数据
store.set('theme', 'dark');
// 读取数据
const theme = store.get('theme');
// 删除数据
store.delete('tempData');
// 清空所有数据
// store.clear();
2. 结构化数据:SQLite
对于复杂的结构化数据(如大量用户数据、关系型数据),推荐使用 SQLite,配合 better-sqlite3 模块:
bash
运行
npm install better-sqlite3 --save
javascript
运行
// 主进程中使用(渲染进程需通过IPC调用)
const Database = require('better-sqlite3');
const db = new Database('app-data.db'); // 数据库文件
// 初始化表
db.prepare(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`).run();
// 插入数据
function addNote(content) {
const stmt = db.prepare('INSERT INTO notes (content) VALUES (?)');
const result = stmt.run(content);
return result.lastInsertRowid; // 返回新记录ID
}
// 查询数据
function getNotes() {
const stmt = db.prepare('SELECT * FROM notes ORDER BY created_at DESC');
return stmt.all();
}
// 通过IPC暴露给渲染进程
ipcMain.handle('add-note', (event, content) => addNote(content));
ipcMain.handle('get-notes', () => getNotes());
3. 文件存储:JSON/CSV
对于可导出 / 导入的数据(如备份文件),可直接读写 JSON 或 CSV 文件:
javascript
运行
const fs = require('fs').promises;
const path = require('path');
// 数据保存路径(使用应用数据目录,跨平台兼容)
const appDataPath = app.getPath('userData'); // Electron 提供的标准路径
const backupPath = path.join(appDataPath, 'backups');
// 确保目录存在
async function ensureDir(dir) {
try {
await fs.access(dir);
} catch {
await fs.mkdir(dir, { recursive: true });
}
}
// 保存为JSON文件
async function saveAsJSON(data, filename) {
await ensureDir(backupPath);
const filePath = path.join(backupPath, `${filename}.json`);
await fs.writeFile(filePath, JSON.stringify(data, null, 2));
return filePath;
}
// 读取JSON文件
async function loadFromJSON(filename) {
const filePath = path.join(backupPath, `${filename}.json`);
const content = await fs.readFile(filePath, 'utf8');
return JSON.parse(content);
}
三、原生对话框:系统级交互
Electron 封装了系统原生对话框(如文件选择、消息提示),让应用与系统交互更自然。
1. 文件选择对话框
用于让用户选择文件或目录(如打开文件、保存文件):
javascript
运行
// 主进程中
const { dialog } = require('electron');
// 打开文件选择对话框
async function openFileDialog() {
const result = await dialog.showOpenDialog(mainWindow, {
title: '选择文件',
filters: [ // 文件类型过滤
{ name: '文本文件', extensions: ['txt', 'md'] },
{ name: '所有文件', extensions: ['*'] }
],
properties: ['openFile', 'multiSelections'] // 允许选择多个文件
});
if (!result.canceled) {
return result.filePaths; // 返回选中的文件路径数组
}
}
// 保存文件对话框
async function saveFileDialog(defaultName) {
const result = await dialog.showSaveDialog(mainWindow, {
title: '保存文件',
defaultPath: path.join(app.getPath('documents'), defaultName),
filters: [{ name: 'JSON文件', extensions: ['json'] }]
});
if (!result.canceled) {
return result.filePath; // 返回保存路径
}
}
// 通过IPC暴露给渲染进程
ipcMain.handle('open-file', () => openFileDialog());
ipcMain.handle('save-file', (event, name) => saveFileDialog(name));
2. 消息对话框
用于显示提示、警告或错误信息:
javascript
运行
// 主进程中
async function showMessageBox(type, message) {
return dialog.showMessageBox(mainWindow, {
type: type, // 'info', 'warning', 'error', 'question'
title: type === 'error' ? '错误' : '提示',
message: message,
buttons: type === 'question' ? ['确认', '取消'] : ['确定']
});
}
// 使用示例
showMessageBox('info', '操作完成');
showMessageBox('question', '确定要执行此操作吗?').then(result => {
if (result.response === 0) { // 0 是第一个按钮(确认)
console.log('用户确认');
}
});
四、全局快捷键:脱离窗口的操作
全局快捷键允许用户在应用未激活时触发操作(如截图工具的 Ctrl+Shift+A),通过 globalShortcut 模块实现:
javascript
运行
// 主进程中
const { globalShortcut } = require('electron');
// 在应用就绪后注册全局快捷键
app.whenReady().then(() => {
// 注册快捷键:Ctrl+Shift+F 触发搜索
const searchShortcut = globalShortcut.register('CmdOrCtrl+Shift+F', () => {
if (mainWindow) {
mainWindow.show();
mainWindow.webContents.send('trigger-search'); // 通知渲染进程
}
});
// 注册快捷键:Esc 隐藏窗口
const hideShortcut = globalShortcut.register('Escape', () => {
if (mainWindow && mainWindow.isVisible()) {
mainWindow.hide();
}
});
// 检查快捷键是否注册成功
if (!searchShortcut || !hideShortcut) {
console.log('快捷键注册失败');
}
});
// 应用退出前注销所有快捷键
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
注意:全局快捷键可能与系统或其他应用冲突,建议提供自定义快捷键设置功能。
五、调试与日志:排查问题的利器
开发过程中难免遇到问题,掌握调试技巧和日志管理能大幅提升排错效率。
1. 主进程调试
主进程无法直接通过浏览器开发者工具调试,需通过以下方式:
- VS Code 调试配置:在 .vscode/launch.json 中添加:json然后按 F5 即可断点调试主进程。
- 日志输出:使用 console.log 或专业日志库 winston:bash运行javascript运行
2. 渲染进程调试
渲染进程调试与网页调试类似:
- 启动时通过
mainWindow.webContents.openDevTools()自动打开开发者工具; - 使用
Ctrl+Shift+I(Windows/Linux)或Cmd+Opt+I(macOS)手动打开; - 利用
console方法输出日志,使用断点调试 JavaScript。
3. 性能分析
- 渲染性能:通过开发者工具的 Performance 面板录制和分析页面渲染性能;
- 内存泄漏:使用 Memory 面板拍摄堆快照,分析内存泄漏;
- 主进程性能:通过
process.memoryUsage()监控主进程内存使用:javascript运行
六、实战案例:简易笔记应用
结合以上知识,我们来实现一个简易笔记应用,包含以下功能:
- 主窗口显示笔记列表
- 新建 / 编辑笔记的模态窗口
- 数据保存到 SQLite 数据库
- 支持导出笔记为 JSON 文件
- 全局快捷键
Ctrl+N新建笔记
(完整代码略,核心实现思路如下):
- 主进程:创建主窗口和编辑窗口,实现 IPC 通信接口;
- 渲染进程(主窗口):展示笔记列表,发送新建 / 删除笔记请求;
- 渲染进程(编辑窗口):编辑笔记内容,保存时通过 IPC 通知主进程;
- 数据层:使用 SQLite 存储笔记,实现增删改查方法;
- 扩展功能:通过
dialog实现导出功能,globalShortcut注册新建快捷键。
七、总结与下一步
本文介绍了 Electron 中级开发的核心知识点:
- 多窗口管理与交互技巧
- 三种数据持久化方案(键值对、SQLite、文件)
- 原生对话框与系统交互
- 全局快捷键的注册与使用
- 调试与日志的最佳实践
掌握这些内容后,你已经能开发功能完善的桌面应用。下一步可深入学习:
- 应用主题与样式定制(支持深色模式)
- 国际化与多语言支持
- 崩溃监控与错误上报
- 与原生应用集成(如调用外部程序)
中级阶段的关键是将各个功能模块有机结合,注重用户体验和代码可维护性,为后续开发更复杂的应用打下基础。gdnwn.tongdaolzw.com
neidv.tongdaolzw.com
fzxol.tongdaolzw.com
fduea.tongdaolzw.com
tlgll.tongdaolzw.com
od7v9.tongdaolzw.com
ye6d0.tongdaolzw.com
it9od.tongdaolzw.com
i2eab.tongdaolzw.com
y7kra.tongdaolzw.com
hqrvo.tongdaolzw.com
ntjsm.tongdaolzw.com
ws1ng.tongdaolzw.com
rcvrb.tongdaolzw.com
snj2m.tongdaolzw.com
sbz.tongdaolzw.com
4deat.tongdaolzw.com
00i2o.tongdaolzw.com
hscro.tongdaolzw.com
t9mb8.tongdaolzw.com

