Electron+React 搭建桌面应用

创建应用程序

创建 Electron 应用

使用 Webpack 创建新的 Electron 应用程序:

bash 复制代码
npm init electron-app@latest my-new-app -- --template=webpack

启动应用

bash 复制代码
npm start

设置 Webpack 配置

添加依赖包,确保可以正确使用 JSX 和其他 React 功能:

bash 复制代码
npm install --save-dev @babel/core @babel/preset-react babel-loader

修改配置文件:

javascript 复制代码
// 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:

bash 复制代码
npm install --save react react-dom

集成 React 代码

现在可以开始在 Electron 中编写和使用 React 组件了。在 src/app.jsx 中编写 React 代码:

javascript 复制代码
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 中:

javascript 复制代码
import './index.css';
import './app.jsx';
console.log('👋 This message is being logged by "renderer.js", included via webpack');

启动应用

bash 复制代码
npm start

主进程和渲染进程通信

在 Electron 中,渲染进程和主进程之间的通信需要通过预加载脚本 preload.js 来进行,preload.js 的作用就是作为渲染进程和主进程之间通信的桥梁。

渲染进程 -> 主进程

当用户在界面上点击最小化或者最大化,或者更改某些全局设置(如语言偏好、主题等)时,这些更改通常由渲染进程触发,但实际执行由主进程负责。在这种情况下,渲染进程调用主进程的方法以应用这些更改,但不需要等待主进程的回包,因为用户界面的响应可以立即更新,而不必等待设置保存的确认。

预加载脚本

在预加载脚本 preload.js 中定义双方通信方法,使用 ipcRenderer.send 将信息发往主进程:

javascript 复制代码
// 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 调用预加载脚本里的方法:

javascript 复制代码
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 监听事件,并执行具体操作:

javascript 复制代码
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 将信息发往主进程,并将结果返回到渲染进程:

javascript 复制代码
// 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 调用预加载脚本里的方法:

javascript 复制代码
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 监听事件,执行操作并将结果返回给预加载脚本:

javascript 复制代码
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 将消息发送给预加载脚本:

javascript 复制代码
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 监听消息,然后通过回调函数将消息传递给渲染进程:

javascript 复制代码
// 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 来监听消息并作出相应处理:

javascript 复制代码
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;

【1】React | Electron Forge

相关推荐
DT——1 小时前
Vite项目中eslint的简单配置
前端·javascript·代码规范
真的很上进4 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
qq_278063714 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl4 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js
小徐不会写代码4 小时前
vue 实现tab菜单切换
前端·javascript·vue.js
2301_765347545 小时前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
喝旺仔la5 小时前
VSCode的使用
java·开发语言·javascript
辛-夷5 小时前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js
一个很帅的帅哥6 小时前
axios(基于Promise的HTTP客户端) 与 `async` 和 `await` 结合使用
javascript·网络·网络协议·http·async·promise·await
dream_ready7 小时前
linux安装nginx+前端部署vue项目(实际测试react项目也可以)
前端·javascript·vue.js·nginx·react·html5