手把手使用 Vite + React + Electron 构建的现代桌面应用程序模板
一个使用 Vite + React + Electron 构建的现代桌面应用程序模板。
手把手使用 Vite + React + Electron 构建的现代桌面应用程序模板 一 初始化
项目结构
├── electron/ # Electron 主进程相关代码
│ ├── config/ # 配置文件
│ ├── handlers/ # 事件处理程序
│ ├── main.ts # 主进程入口文件
│ └── preload.ts # 预加载脚本
├── src/ # 渲染进程源代码
│ ├── assets/ # 静态资源
│ ├── App.tsx # 主应用组件
│ └── main.tsx # 渲染进程入口文件
├── public/ # 静态资源目录
└── dist-electron/ # 编译后的 Electron 代码
1. 为什么需要窗口管理 WindowManager?
1.1 解决的核心问题
-
窗口管理混乱
- 传统方式下每个窗口独立创建和管理,容易造成代码重复和管理混乱
- 不同窗口之间状态同步困难
- 窗口生命周期管理复杂
-
通信机制不统一
- 窗口间通信方式不一致
- IPC 事件分散在各处
- 缺乏统一的事件处理机制
-
配置重复
- 窗口配置分散
- 相似配置大量重复
- 维护成本高
1.2 设计目标
-
统一管理
class WindowManager { main: BrowserWindow | null = null; group = new Map(); }为什么这样设计?
- 单一职责原则:一个类专注于窗口管理
- 集中管理:所有窗口状态在一处维护
- 便于扩展:统一的接口便于添加新功能
-
灵活配置
type WindowOptions = BrowserWindowConstructorOptions & { id?: number; isMainWin?: boolean; route?: string; isMultiWindow?: boolean; parentId?: number; maximize?: boolean; };为什么需要这些配置?
isMainWin: 主窗口需要特殊处理(如关闭行为)route: 支持路由化管理,实现窗口复用isMultiWindow: 控制是否允许多个相同窗口parentId: 支持父子窗口关系,实现模态窗口
2. 如何实现?
2.1 窗口创建的智能处理
createWindow(options?: WindowOptions) {
// 1. 配置合并
const args = Object.assign({}, this.windowOptionsConfig, options);
// 2. 窗口复用检查
for (const i in this.group) {
const currentWindow = this.getWindow(Number(i));
const { route, isMultiWindow } = this.group.get(i);
if (currentWindow && route === args.route && !isMultiWindow) {
currentWindow.focus();
return currentWindow;
}
}
// 3. 创建新窗口
const win = new BrowserWindow(args);
// 4. 注册到管理器
this.group.set(win.id, {
route: args.route || '',
isMultiWindow: args.isMultiWindow || false,
});
}
实现要点:
-
配置合并:为什么使用 Object.assign?
- 保留默认配置
- 允许覆盖自定义配置
- 确保配置完整性
-
窗口复用:为什么要复用?
- 减少资源消耗
- 提升应用性能
- 确保功能一致性
-
状态管理:为什么使用 Map?
- 高效的键值对存储
- 便于查找和更新
- 支持动态属性
2.2 IPC 通信设计
const Events = {
WINDOW_NEW: 'WINDOW_NEW',
WINDOW_CLOSED: 'WINDOW_CLOSED',
// ...
} as const;
initIpcHandlers() {
ipcMain.handle(Events.WINDOW_CLOSED, (_event, winId) => {
if (winId) {
this.getWindow(Number(winId))?.close();
this.group.delete(winId);
} else {
this.closeAllWindow();
}
});
}
为什么这样设计?
- 常量枚举:避免字符串错误
- 统一处理:集中的事件管理
- 类型安全:TypeScript 类型检查
3. 如何使用?
3.1 基础使用
// 1. 创建管理器实例
const windowManager = new WindowManager();
// 2. 初始化 IPC 处理器
windowManager.initIpcHandlers();
// 3. 创建主窗口
const mainWindow = windowManager.createWindow({
isMainWin: true,
width: 800,
height: 600
});
3.2 创建子窗口
// 从渲染进程创建新窗口
ipcRenderer.invoke(Events.WINDOW_NEW, {
route: '/settings',
width: 400,
height: 300,
parentId: currentWindowId
});
3.3 窗口通信
// 在渲染进程中
// 最小化窗口
ipcRenderer.invoke(Events.WINDOW_MINI, windowId);
// 关闭窗口
ipcRenderer.invoke(Events.WINDOW_CLOSED, windowId);
// 获取窗口边界
const bounds = await ipcRenderer.invoke(Events.WINDOW_GET_BOUNDS);
3.4 最佳实践
-
窗口创建
// 好的实践 const settingsWindow = windowManager.createWindow({ route: '/settings', isMultiWindow: false, // 确保只有一个设置窗口 width: 400, height: 300 }); // 避免这样做 const win = new BrowserWindow({ width: 400, height: 300 }); -
事件处理
// 好的实践 windowManager.initIpcHandlers(); // 清理时 windowManager.destroyIpcHandlers(); // 避免这样做 ipcMain.on('some-event', () => {}); // 没有统一管理 -
窗口管理
// 好的实践 if (windowManager.getWindow(windowId)) { windowManager.getWindow(windowId).focus(); } // 避免这样做 const allWindows = BrowserWindow.getAllWindows(); const targetWindow = allWindows.find(w => w.id === windowId);
4. 实际应用场景
4.1 主窗口应用
// app.ts
const windowManager = new WindowManager();
app.on('ready', () => {
windowManager.initIpcHandlers();
// 创建主窗口
windowManager.createWindow({
isMainWin: true,
route: '/',
width: 1200,
height: 800,
maximize: true
});
});
// 确保应用退出时清理资源
app.on('window-all-closed', () => {
windowManager.destroyIpcHandlers();
if (process.platform !== 'darwin') {
app.quit();
}
});
4.2 模态窗口
// 在渲染进程中打开设置窗口
async function openSettings() {
const currentWindowId = await window.electron.getCurrentWindowId();
window.electron.createWindow({
route: '/settings',
width: 500,
height: 400,
parentId: currentWindowId,
modal: true, // 模态窗口
resizable: false
});
}
4.3 多窗口应用
// 打开多个文档窗口
function openDocument(documentId: string) {
windowManager.createWindow({
route: `/document/${documentId}`,
isMultiWindow: true, // 允许多个文档窗口
width: 800,
height: 600
});
}
5. 总结
WindowManager 的设计理念是"统一管理、灵活配置、简单使用"。通过:
- 集中的窗口管理
- 统一的通信机制
- 灵活的配置系统
解决了 Electron 应用中窗口管理的常见问题。使用时要注意:
- 合理使用窗口复用机制
- 正确处理窗口生命周期
- 及时清理资源
通过遵循这些原则和最佳实践,可以构建出稳定、高效的多窗口 Electron 应用。