Electron 调试工具
开启 DevTools
渲染进程 DevTools
// main.js - 随时打开 DevTools
mainWindow.webContents.openDevTools();
// 或者在特定条件时开启
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools();
}
主进程调试
主进程使用 Node.js 调试器:
# 启动时开启调试端口
electron --inspect=5858 .
# 或者调试并暂停
electron --inspect-brk .
然后在 VS Code 或 Chrome DevTools 中连接:
# Chrome DevTools 连接
chrome://inspect
VS Code 调试配置
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Main Process",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"runtimeArgs": ["."],
"env": {
"NODE_ENV": "development"
},
"preLaunchTask": "${defaultBuildTask}"
},
{
"type": "chrome",
"request": "launch",
"name": "Debug Renderer Process",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
主进程调试
使用 electron-log
const log = require('electron-log');
// 配置日志
log.transports.file.level = 'debug';
log.transports.file.maxSize = 5 * 1024 * 1024; // 5MB
log.transports.console.level = 'debug';
// 格式化
log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}';
// 主进程日志
log.info('Application starting...');
log.warn('Deprecated API used');
log.error('Failed to load module', { error: err.message });
远程调试主进程
// 在主进程中开启调试服务器
const { app } = require('electron');
if (process.env.NODE_ENV === 'development') {
app.commandLine.appendSwitch('inspect', '5858');
app.commandLine.appendSwitch('inspect-brk', '5858');
}
常见主进程错误处理
process.on('uncaughtException', (error) => {
log.error('Uncaught Exception:', error);
dialog.showErrorBox('Error', `An unexpected error occurred: ${error.message}`);
app.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
log.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
渲染进程调试
Webpack 源码映射
// webpack.main.config.js
module.exports = {
// ...
devtool: 'source-map' // 或 'eval-source-map'
};
React DevTools
安装 React DevTools 扩展:
// main.js - 加载 React DevTools
const path = require('path');
BrowserWindow.addDevToolsExtensions(
path.join(__dirname, 'react-devtools-extensions')
);
Redux DevTools(如果是 Redux 应用)
// preload.js - 如果需要 Redux DevTools
contextBridge.exposeInMainWorld('__REDUX_DEVTOOLS_EXTENSION__', {
connect: () => {
// Redux DevTools 连接逻辑
}
});
内存泄漏排查
常见内存泄漏场景
1. 事件监听未移除
// 错误:事件监听重复注册
function onClickHandler() {
document.addEventListener('click', handleClick); // 每次调用都注册
}
// 正确:在组件卸载时移除
class MyComponent {
constructor() {
this.handleClick = this.handleClick.bind(this);
document.addEventListener('click', this.handleClick);
}
destroy() {
document.removeEventListener('click', this.handleClick);
}
}
2. 定时器未清除
// 错误
setInterval(() => {
updateData();
}, 1000);
// 正确:保存引用,需要时清除
this.intervalId = setInterval(() => {
updateData();
}, 1000);
// 在组件销毁时
clearInterval(this.intervalId);
3. 闭包引用
// 错误:闭包持有大量数据
function createHandler() {
const largeData = loadLargeData(); // 占用大量内存
return () => {
console.log('Handler executed');
// largeData 永远不会被释放
};
}
// 正确:使用 WeakMap 或及时清理
const handlerWeakMap = new WeakMap();
function createHandler() {
const largeData = loadLargeData();
const handler = () => {
console.log('Handler executed');
};
handlerWeakMap.set(handler, largeData);
return handler;
}
内存分析工具
使用 Chrome DevTools Memory 面板:
- Heap Snapshot:拍摄内存快照,比较对象
- Allocation Timeline:记录内存分配时间线
- Sampling Profile:采样分析内存使用
// 在代码中添加标记,便于在 DevTools 中识别
console.memory // 查看当前内存使用
CPU 性能问题
主线程阻塞
// 错误:在主线程执行耗时操作
ipcMain.handle('process-data', async (event, hugeArray) => {
return hugeArray.reduce((acc, item) => {
// 同步处理大量数据,会阻塞 UI
return acc + processItem(item);
}, 0);
});
// 正确:使用 Worker 或分片处理
ipcMain.handle('process-data', async (event, hugeArray) => {
return new Promise((resolve) => {
// 分批处理
const batchSize = 1000;
let index = 0;
function processBatch() {
const batch = hugeArray.slice(index, index + batchSize);
const result = batch.reduce((acc, item) => acc + processItem(item), 0);
index += batchSize;
if (index < hugeArray.length) {
// 让出主线程
setImmediate(processBatch);
} else {
resolve(result);
}
}
processBatch();
});
});
Web Worker 使用
// worker.js
self.onmessage = function(e) {
const result = heavyComputation(e.data);
self.postMessage(result);
};
// main.js 或 renderer.js
const worker = new Worker('worker.js');
worker.postMessage(hugeData);
worker.onmessage = function(e) {
console.log('Result:', e.data);
};
渲染性能优化
减少重排和重绘
// 错误:多次触发重排
function updateList(items) {
const container = document.getElementById('list');
container.innerHTML = '';
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.name;
container.appendChild(div); // 每次 append 触发重排
});
}
// 正确:使用 DocumentFragment 或一次性更新
function updateList(items) {
const container = document.getElementById('list');
const fragment = document.createDocumentFragment();
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.name;
fragment.appendChild(div);
});
container.innerHTML = '';
container.appendChild(fragment); // 只触发一次重排
}
// 或者使用 display: none 隐藏后更新
container.style.display = 'none';
container.innerHTML = newHTML;
container.style.display = '';
图片优化
// 使用懒加载
const lazyImage = new Image();
lazyImage.src = 'large-image.jpg';
lazyImage.onload = () => {
imageElement.src = lazyImage.src;
};
// 使用 srcset
// <img srcset="small.jpg 480w, large.jpg 1080w" sizes="(max-width: 600px) 480px, 1080px">
虚拟列表
对于大量数据,使用虚拟滚动:
// react-window 或 react-virtualized
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList
height={400}
itemCount={items.length}
itemSize={50}
width={300}
>
{({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)}
</FixedSizeList>
);
}
启动性能优化
延迟加载
// main.js
app.whenReady().then(() => {
// 只加载必要的模块
require('./main-window');
// 延迟加载其他功能
setTimeout(() => {
require('./background-tasks');
require('./tray');
}, 5000);
});
预加载优化
// preload.js 应该尽量轻量
// 只暴露必要的 API,避免在 preload 中做重计算
// preload.js - 轻量化
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// 只暴露必要的方法
readFile: (path) => ipcRenderer.invoke('file:read', path)
// 不要在这里添加复杂逻辑
});
窗口显示优化
const mainWindow = new BrowserWindow({
show: false, // 先不显示
backgroundColor: '#ffffff'
});
mainWindow.once('ready-to-show', () => {
mainWindow.show(); // 渲染完成后再显示
});
IPC 性能
避免频繁 IPC 调用
// 错误:每次输入都 IPC
input.addEventListener('input', (e) => {
ipcRenderer.invoke('search', e.target.value); // 频繁调用
});
// 正确:使用防抖
import { debounce } from 'lodash';
const debouncedSearch = debounce((query) => {
ipcRenderer.invoke('search', query);
}, 300);
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
批量 IPC
// 错误:多次小请求
async function loadData() {
const user = await ipcRenderer.invoke('get-user');
const posts = await ipcRenderer.invoke('get-posts');
const settings = await ipcRenderer.invoke('get-settings');
}
// 正确:合并为一次请求
ipcMain.handle('get-all-data', async () => {
return {
user: await getUser(),
posts: await getPosts(),
settings: await getSettings()
};
});
async function loadData() {
const data = await ipcRenderer.invoke('get-all-data');
}
这一章想说的
Electron 调试与性能优化:
- 调试工具:主进程调试、渲染进程 DevTools、VS Code 配置
- 内存泄漏:事件监听、定时器、闭包是常见原因
- CPU 问题:主线程阻塞时使用 Worker 或分片处理
- 渲染优化:减少重排重绘、虚拟列表、图片懒加载
- IPC 优化:避免频繁调用、使用防抖、批量请求
性能问题需要用工具定位,DevTools 是最重要的调试武器。