Electron+React框架搭建以及基础使用

前言

大家好,我是JS-Man

这篇文章将教小伙伴们搭建一个较完善的Electron+React的框架。

脚手架结构清晰功能健全,打包工具使用的是官方推荐的Electron Forge

本文目录分为三大块内容:

  1. Electron的基础使用和原理。
  2. 使用Electron Forge搭建脚手架。
  3. 项目实战

如果使用过Electron的,可以跳过第一部分。

1. Electron基础

来自官网的介绍:

Electron将浏览器内核和Node.js打包进了客户端里,所以说对于会三件套的前端来说,上手Electron是0成本的,基础教程的目录结构如下。

下面开始我们的基础教程:

1.1 主进程

npm init后安装下electronnpm install electron --save-dev

package.json 中指定main文件 是 Electron 应用的入口。 这个文件控制主程序 (main process) ,它运行在 Node.js 环境里,负责控制应用的生命周期、显示原生界面、执行特殊操作并管理渲染器进程 (renderer processes)。

此时package.json如下:

js 复制代码
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^35.1.5"
  }
}

electron .这个命令会告诉 Electron 在当前目录下寻找主脚本,并以开发模式运行它。

1.2 网页加载BrowserWindow

在Electron中,每个窗口展示一个页面,可以是本地HTML也可以是远程URL,本地文件的场景居多,在根目录创建一个index.html文件:

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>来自 Electron 渲染器的问好!</title>
  </head>
  <body>
    <h1>来自 Electron 渲染器的问好!</h1>
    <p>👋</p>
    <p id="info"></p>
  </body>
</html>

有了一个网页,可以将其加载到Electron的BrowserWindow。 修改main.js为:

js 复制代码
// app模块控制着您应用程序的事件生命周期。
// BrowserWindow这个模块创建和管理 app 的窗口。
// Electron 28 起,Electron 支持 ECMAScript 模块
const { app, BrowserWindow } = require('electron')

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })

  win.loadFile('index.html')
}

// 在 Electron 中,只有在 app 模块的ready事件(event)触发后才能创建 BrowserWindows 实例
app.whenReady().then(() => {
  createWindow()
})

此时执行npm run start已经可以启动客户端了,每个页面都在独立的进程中运行。

通过 Node.js 的 process.platform 变量,可以针对特定平台运行特定代码,此文不作讨论。

1.3 预加载脚本

也就是说,预加载脚本是主进程和渲染进程通信的桥梁。

从 Electron 20 开始,预加载脚本默认沙盒化 ,不再拥有完整 Node.js 环境的访问权,预加载脚本在渲染器加载网页之前注入。

为了演示这一概念,将会创建一个将应用中的 Chrome、Node、Electron 版本号暴露至渲染器的预加载脚本:

新建一个 preload.js 文件。该脚本通过 versions 这一全局变量,将 Electron 的 process.versions 对象暴露给渲染器。

js 复制代码
// preload.js
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('versions', {
  node: () => process.versions.node,
  chrome: () => process.versions.chrome,
  electron: () => process.versions.electron
  // 除函数之外,我们也可以暴露变量
})

为了将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。

js 复制代码
// main.js
const { app, BrowserWindow } = require('electron')
const path = require('node:path')

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  win.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
})

此时,渲染器就能全局访问versions了,创建一个renderer.js

js 复制代码
// renderer.js
const information = document.getElementById('info')
information.innerText = `本应用正在使用 Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), 和 Electron (v${versions.electron()})`

index.html里加载:

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>来自 Electron 渲染器的问好!</title>
  </head>
  <body>
    <h1>来自 Electron 渲染器的问好!</h1>
    <p>👋</p>
    <p id="info"></p>
  </body>
  <script src="./renderer.js"></script>
</html>

此时客户端界面如下:

1.4 进程间通信

Electron 的主进程和渲染进程有着清楚的分工并且不可互换。 这代表着无论是从渲染进程直接访问 Node.js 接口,亦或者是从主进程访问 HTML 文档对象模型 (DOM),都是不可能的。

解决这一问题的方法是使用进程间通信 (IPC)。可以使用 Electron 的 ipcMain 模块和 ipcRenderer 模块来进行进程间通信。

为了从你的网页向主进程发送消息,你可以使用 ipcMain.handle 设置一个主进程处理程序(handler),然后在预处理脚本中暴露一个被称为 ipcRenderer.invoke 的函数来触发该处理程序(handler)。

首先,在预处理脚本中设置 invoke 调用:

js 复制代码
// preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('versions', {
  node: () => process.versions.node,
  chrome: () => process.versions.chrome,
  electron: () => process.versions.electron,
  ping: () => ipcRenderer.invoke('ping')
  // 除函数之外,我们也可以暴露变量
})

然后在主进程中处理监听器:

js 复制代码
// main.js
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  win.loadFile('index.html')
}
app.whenReady().then(() => {
  ipcMain.handle('ping', () => 'pong')
  createWindow()
})

发送器与接收器设置完成之后,渲染进程就可以和主进程通信了:

js 复制代码
// renderer.js
const func = async () => {
  const response = await window.versions.ping()
  console.log(response) // 打印 'pong'
}

func()

客户端里打印如下:

2 搭建脚手架

2.1 Electron Forge

直接使用Electron Forge官方推荐的脚手架,官方有四个模版:

这里我们使用webpack-typescript,直接执行npx create-electron-app@latest my-app --template=webpack-typescript,目录结构如下:

关于webpack的配置可以先不看,重点关注下forge.config.ts这个文件,里面指定了preload和html的入口文件,和第一部分章节里不一样的是,renderer也帮我们指定了。

2.2 集成React

我们手动安装下React相关依赖:npm i react@18 react-dom@18 react-router-dom@5 @types/react-router-dom@5

接着增加app.tsx,渲染器使用react加载,app.tsx代码如下:

js 复制代码
// app.tsx
import { createRoot } from 'react-dom/client'
import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import ComponentLoader from './components/ComponentLoader'
import RobotGrab from './pages/robot-grab'
import NotFound from './components/NotFound'

const App: React.FC = () => {
  return (
    <Router>
      <React.Suspense fallback={<ComponentLoader />}>
        <Switch>
          <Route path="/" component={RobotGrab} />
          <Route path="/robot-grab" component={RobotGrab} />
          <Route path="*" component={NotFound} />
        </Switch>
      </React.Suspense>
    </Router>
  )
}

const root = createRoot(document.getElementById('root'))
root.render(<App />)

修改index.html如下:

js 复制代码
// index.html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Electron</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

同时,renderer.ts也需要加载app.tsx

js 复制代码
// Add this to the end of the existing file
import './app'

现在就可以使用react进行渲染了,完整的项目结构如下:

3.项目实战

下面进入一个实际的业务场景,一个自动化脚本的需求,脚本使用了Playwright,集成在了Electron里。

首先使用React画页面,添加一个按钮

js 复制代码
// src/pages/grab/index.tsx

const handleAction = () => {
    window.robotGrab
      .launch()
      .then((result: unknown) => {
        const res = result as { success?: boolean; message?: string }
        if (res && res.success) {
          setMessage(res.message || '成功连接到Chrome浏览器')
          setBrowserStatus('ready')
        } else {
          setMessage(res?.message || '连接失败,请检查Chrome是否已启动并开启远程调试')
          setBrowserStatus(null)
        }
      })
      .catch((error: Error) => {
        console.error('启动失败', error)
        setMessage(`启动失败: ${error.message || '连接Chrome失败'}`)
        setBrowserStatus(null)
      })
  }

<button onClick={() => handleAction()}>启动浏览器</button>

调用的launch就是preload.ts注入进window的:

js 复制代码
// src/preload.ts

import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('robotGrab', {
  launch: () => ipcRenderer.invoke('launch'),
})

接着在主进程里处理:

js 复制代码
// src/index.ts

// 处理启动脚本
  ipcMain.handle('launch', async () => {
    try {
      console.log(`尝试启动脚本,调试端口: ${debugPort}`)
      await openOrLoginScript(debugPort)
      return { success: true, message: '成功连接到Chrome并打开页面' }
    } catch (error) {
      console.error('启动失败:', error)
      return {
        success: false,
        message: `启动失败: ${error.message || '未知错误'}`,
        error: error.toString(),
      }
    }
  })

实际的脚本执行放在scripts文件夹里,这里就不粘贴脚本代码了。

总结

看完这篇文章的小伙伴,对Electron肯定能够上手使用了,具体的业务代码没有在本文呈现,也没有必要,如果大家对Electron比较感兴趣,那我会出一篇Electron原理篇,深入剖析下主进程、渲染进程和Electron的弊端。

相关推荐
恋猫de小郭23 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端