创建应用程序
创建 Electron 应用
使用 Webpack 创建新的 Electron 应用程序:
npm init electron-app@latest my-new-app -- --template=webpack
启动应用
npm start
设置 Webpack 配置
添加依赖包,确保可以正确使用 JSX 和其他 React 功能:
npm install --save-dev @babel/core @babel/preset-react babel-loader
修改配置文件:
// webpack.rules.js
module.exports = [
// ... existing loader config ...
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
exclude: /node_modules/,
presets: ['@babel/preset-react']
}
}
}
// ... existing loader config ...
];
集成前端 React
添加 React 依赖
将基本的 React 包添加到 dependencies:
npm install --save react react-dom
集成 React 代码
现在可以开始在 Electron 中编写和使用 React 组件了。在 src/app.jsx
中编写 React 代码:
import * as React from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.body);
root.render(<h2>Hello from React!</h2>);
添加渲染进程
将 React 代码添加到渲染进程 src/renderer.js
中:
import './index.css';
import './app.jsx';
console.log('👋 This message is being logged by "renderer.js", included via webpack');
启动应用
npm start
主进程和渲染进程通信
在 Electron 中,渲染进程和主进程之间的通信需要通过预加载脚本 preload.js
来进行,preload.js
的作用就是作为渲染进程和主进程之间通信的桥梁。
渲染进程 -> 主进程
当用户在界面上点击最小化或者最大化,或者更改某些全局设置(如语言偏好、主题等)时,这些更改通常由渲染进程触发,但实际执行由主进程负责。在这种情况下,渲染进程调用主进程的方法以应用这些更改,但不需要等待主进程的回包,因为用户界面的响应可以立即更新,而不必等待设置保存的确认。
预加载脚本
在预加载脚本 preload.js
中定义双方通信方法,使用 ipcRenderer.send
将信息发往主进程:
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
const {contextBridge, ipcRenderer} = require('electron');
contextBridge.exposeInMainWorld('my_app_name', {
// 最小化
minimize: () => {
ipcRenderer.send('minimize')
},
// 最大化
maximize: () => {
ipcRenderer.send('maximize')
}
})
渲染进程
在渲染进程中,使用 window.app_name.func_name
调用预加载脚本里的方法:
import * as React from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.body);
root.render(<div>
<button onClick={() => {
window.my_app_name.minimize()
}}>最小化</button>
<button onClick={() => {
window.my_app_name.maximize()
}}>最大化</button>
</div>);
主进程
在主进程中,使用 ipc.Main.on
监听事件,并执行具体操作:
const {app, BrowserWindow, ipcMain} = require('electron');
const path = require('node:path');
let mainWindow;
const createWindow = () => {
// 创建浏览器窗口。
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, // 预加载脚本,用于在主进程和渲染进程之间安全地共享数据
},
});
// 加载应用的index.html。
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
// 打开开发者工具。
mainWindow.webContents.openDevTools();
};
// .....其他代码.....
// 监听来自渲染进程的'minimize'消息,最小化主窗口。
ipcMain.on('minimize', event => {
mainWindow.minimize();
})
// 监听来自渲染进程的'maximize'消息,最大化主窗口。
ipcMain.on('maximize', event => {
mainWindow.maximize();
})
渲染进程 <-> 主进程
在 Electron 中,用户可能需要打开、保存或者删除文件。当用户点击一个按钮或者菜单项来执行这些操作时,渲染进程会向主进程发送一个消息,请求执行相应的操作。主进程接到消息后,调用系统API来完成文件操作,并将操作结果发送回渲染进程,渲染进程根据主进程的响应更新界面状态等。
预加载脚本
在预加载脚本 preload.js
中定义双方通信方法,使用 ipcRenderer.invoke
将信息发往主进程,并将结果返回到渲染进程:
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
const {contextBridge, ipcRenderer} = require('electron');
contextBridge.exposeInMainWorld('my_app_name', {
// 选择文件
chooseFile: async () => {
const path = await ipcRenderer.invoke('choose-file');
return path;
},
})
渲染进程
在渲染进程中,使用 window.app_name.func_name
调用预加载脚本里的方法:
import * as React from 'react';
import {createRoot} from 'react-dom/client';
const root = createRoot(document.body);
root.render(<div>
<button onClick={() => {
window.my_app_name.chooseFile().then(result => {
console.log(result['filePaths'][0])
alert(result['filePaths'][0])
})
}}>选择文件
</button>
</div>);
主进程
在主进程中,使用 ipc.handle
监听事件,执行操作并将结果返回给预加载脚本:
const {app, BrowserWindow, ipcMain, dialog} = require('electron');
const path = require('node:path');
let mainWindow;
const createWindow = () => {
// 创建浏览器窗口。
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, // 预加载脚本,用于在主进程和渲染进程之间安全地共享数据
},
});
// 加载应用的index.html。
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
// 打开开发者工具。
mainWindow.webContents.openDevTools();
};
// .....其他代码.....
// 监听来自渲染进程的'choose-file'消息,选择文件,并将结果返回给预加载脚本。
ipcMain.handle('choose-file', async (event, data) => {
const result = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
properties: ['openFile']
});
return result
});
主进程 -> 渲染进程
Electron 应用启动时,有时候需要由主进程从本地存储或者服务器来加载应用的配置信息(窗口大小、位置、主题设置等)。主进程加载完设置后,通过单向通信将配置信息发送给渲染进程。渲染进程接收到配置信息后,根据这些信息调整界面布局或者样式。通常用于初始化或者更新应用的界面设置。
主进程
在主进程中,使用 mainWindow.webContents.send
将消息发送给预加载脚本:
const {app, BrowserWindow, ipcMain, dialog} = require('electron');
const path = require('node:path');
let mainWindow;
const createWindow = () => {
// 创建浏览器窗口。
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, // 预加载脚本,用于在主进程和渲染进程之间安全地共享数据
},
});
// 加载应用的index.html。
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
// 打开开发者工具。
mainWindow.webContents.openDevTools();
// 设置十秒钟后发送消息到渲染进程
setTimeout(() => {
mainWindow.webContents.send('message-from-main', 'Hello from main process!');
}, 3000); // 10000 毫秒等于 10 秒
};
// .....其他代码.....
预加载脚本
在预加载脚本 preload.js
中使用 ipcRenderer.on
监听消息,然后通过回调函数将消息传递给渲染进程:
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
const {contextBridge, ipcRenderer} = require('electron');
contextBridge.exposeInMainWorld('my_app_name', {
msgFromMain: (callback) => ipcRenderer.on('message-from-main', (event, message) => {
callback(message);
})
})
渲染进程
在渲染进程中,使用 useEffect
来监听消息并作出相应处理:
import React, { useEffect, useState } from 'react';
const App = () => {
const [messageFromMain, setMessageFromMain] = useState('');
useEffect(() => {
// 调用预加载脚本中暴露的 msgFromMain 方法
window.my_app_name.msgFromMain((message) => {
setMessageFromMain(message);
});
// 清理函数(可选),如果需要的话可以在这里注销监听器
return () => {
// 如果有必要的话,可以在这里注销监听器,但在这个例子中我们不需要,因为ipcRenderer.on会自动管理监听器的生命周期
};
}, []); // 确保这个 effect 只运行一次
return (
<div>
{/* 显示从主进程接收到的消息 */}
<p>{messageFromMain}</p>
</div>
);
};
export default App;