Electron+React实战跨平台应用
Electron+React实战跨平台应用
应用架构
md和xlsx文档编辑并自动上传保存到七牛云

DEMO
脚手架
1 | git clone https://github.com/electron/electron-quick-start |
- 执行npm install安装node_modules会报错,按照如下命令安装并启动成功
1 | //使用淘宝镜像 |
- Demo的图片
进程和线程
线程是操作系统能够调度的最小单位,被包含在进程之中。一个进程可以有多个线程
一个应用程序可以有多个进程。
js是异步单线程
进程之间内存很难共享,通信也不方便(可以使用IPC实现)。

创建BrowserWindow和代码流程分析
要是的nodejs支持热更新,需要使用nodemon
1 | 1、安装nodemon |
elecrton底层基于nodejs,因此main.js是commonjs规范,引入是require,导出时module.exports。
代码执行流程分析
1、执行npm start运行main.js文件开启主进程。
2、main.js流程分析
1 | // Modules to control application life and create native browser window |
1 | 2.4 renderer.js |
嵌套window
1 | // Modules to control application life and create native browser window |
DOMContentLoaded和onload的区别
他们的区别是,触发的时机不一样,先触发DOMContentLoaded事件,后触发load事件。
DOM文档加载的步骤为
解析HTML结构。
加载外部脚本和样式表文件。
解析并执行脚本代码。
DOM树构建完成。//DOMContentLoaded
加载图片等外部文件。
页面加载完毕。//load
在第4步,会触发DOMContentLoaded事件。在第6步,触发load事件。
进程间通信
https://www.cnblogs.com/baixinL/p/14276580.html
安装 devtron,类型redux-dev-tools的浏览器插件,是electron的开发者工具方便查看和模拟进程间通信
1 | npm i -D devtron |
开发者工具多了一个devtron面板

使用IPC进行进程通信
类似订阅监听模式,但是main process只能通过reply方式回复,不能调用ipcRenderer.send
1 | //from |

使用remote模块实现跨进程访问(由于安全性问题,最新版本已经被废弃)
ipcMain和ipcRenderer方式太麻烦。使用require(‘electron’).remote可以获取到main process的独有的api.
例如
1 | const {BrowserWindow} = require('elecrton').remote |
webContents.send发送
1 | // 在主进程中. |
electron设置菜单不显示
https://blog.csdn.net/qq_42597536/article/details/116017264
React学习
略
- npx npm cnpm yarn区别,详见https://blog.csdn.net/lixiaolong240035/article/details/98472531
调试
详见:https://www.cnblogs.com/vickylinj/p/14087734.html
渲染线程
webContents.openDevTools即可
主线程
使用inspect
真实项目
需求分析


环境搭建
ts环境搭建
npx create-react-app doc-editor --template typescript
添加代码规范配置和git commit配置,规范代码结构和提交,详见ts实战项目.md
1 | { |
elecrton环境搭建
mainwindow加载localhost:3000网址
由于使用的是create-react-app脚手架,不能采用electron脚手架,需要手动安装electron和devtron并添加js文件
1、根目录下新建main.js文件,使用electron-is-dev来区分开发环境和生成环境
1 | const {app,BrowserWindow} = require('electron') |
2、设置package.json
1 | { |
3、开两个终端,第一个终端先执行npm start,第二个终端后执行npm run dev
如此即可进行联动起来。
4、上面有两个问题,需要开两个终端,而且要先启动服务,才能运行electron
使用concurrently可以同时运行这两个命令npm install concurrently --save
修改dev命令如下,使用\转移双引号
"dev": "concurrently \"electron .\" \"npm start\""
这样只需要执行npm run dev就可以同时启动react和electron
但是由于react启动比较慢,所以electron启动会有白屏等待react
5、安装wait on解决白屏问题
npm install -D wait-on
修改命令
"dev": "concurrently \"wait-on http://localhost:3000 && electron .\" \"npm start\""
等待http://localhost:3000加载完毕后再运行electron
6、每次都要打开浏览器,如何解决
react有个环境变量BROWSER可以设置
但是linux和window用到环境变量设置的书写机制不同,没法用一个命令去写。需要引入cross-env组件来解决环境变量跨平台问题
npm install -D cross-env
修改命令
"dev": "concurrently \"wait-on http://localhost:3000 && electron .\" \"cross-env BROWSER=none npm start\""
这样一个electron和react结合的完美开发环境就搭建好了,支持热更新。愉快的敲代码吧
文件结构和代码规范
- 没有固定标准
- 不要超过5分钟思考
- 避免多层嵌套
样式库选择Bootstrap
npm installl bootstrap
在App.tsx上面引入bootstrap样式
import "bootstrap/dist/css/bootstrap.min.css"
Bootstrap基础知识
loading图标使用
<div className="spinner-border" role="status" style={{ visibility: isLoading ? "visible" : "hidden" }} > <span className="sr-only">Loading...</span> </div><div class="container"> ... </div>1
2
3
4
5
6
7
8
9
10
11
12
13
*
* d-flex
* align-items-center
* margin padding简写 p-0 pl-0 p-1 m-0 m-1 ml-1,详见https://blog.csdn.net/mp624183768/article/details/84685097
* 布局容器:Bootstrap 需要为页面内容和栅格系统包裹一个 `.container` 容器。我们提供了两个作此用处的类。注意,由于 `padding` 等属性的原因,这两种 容器类不能互相嵌套。
`.container` 类用于固定宽度并支持响应式布局的容器。<div class="container-fluid"> ... </div>1
2
3
`.container-fluid` 类用于 100% 宽度,占据全部视口(viewport)的容器。我们的软件视口是变化的,因此使用container-fluid- 使用col-md col-xs col-lg可以实现类似媒介查询效果 - 使用bg-xxx设置背景色1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
* 栅格系统:栅格系统用于通过一系列的行(row)与列(column)的组合来创建页面布局,你的内容就可以放入这些创建好的布局中。下面就介绍一下 Bootstrap 栅格系统的工作原理:
- “行(row)”必须包含在 `.container` (固定宽度)或 `.container-fluid` (100% 宽度)中,以便为其赋予合适的排列(aligment)和内补(padding)。
- 通过“行(row)”在水平方向创建一组“列(column)”。
- 你的内容应当放置于“列(column)”内,并且,只有“列(column)”可以作为行(row)”的直接子元素。
- 类似 `.row` 和 `.col-xs-4` 这种预定义的类,可以用来快速创建栅格布局。Bootstrap 源码中定义的 mixin 也可以用来创建语义化的布局。
- 通过为“列(column)”设置 `padding` 属性,从而创建列与列之间的间隔(gutter)。通过为 `.row` 元素设置负值 `margin` 从而抵消掉为 `.container` 元素设置的 `padding`,也就间接为“行(row)”所包含的“列(column)”抵消掉了`padding`。
- 负值的 margin就是下面的示例为什么是向外突出的原因。在栅格列中的内容排成一行。
- 栅格系统中的列是通过指定1到12的值来表示其跨越的范围。例如,三个等宽的列可以使用三个 `.col-xs-4` 来创建。
- 如果一“行(row)”中包含了的“列(column)”大于 12,多余的“列(column)”所在的元素将被作为一个整体另起一行排列。
- 栅格类适用于与屏幕宽度大于或等于分界点大小的设备 , 并且针对小屏幕设备覆盖栅格类。 因此,在元素上应用任何 `.col-md-*` 栅格类适用于与屏幕宽度大于或等于分界点大小的设备 , 并且针对小屏幕设备覆盖栅格类。 因此,在元素上应用任何 `.col-lg-*` 不存在, 也影响大屏幕设备。
- ```html
实例:从堆叠到水平排列
使用单一的一组 .col-md-* 栅格类,就可以创建一个基本的栅格系统,在手机和平板设备上一开始是堆叠在一起的(超小屏幕到小屏幕这一范围),在桌面(中等)屏幕设备上变为水平排列。所有“列(column)必须放在 ” .row 内。
.col-md-1.col-md-1.col-md-1.col-md-1.col-md-1.col-md-1.col-md-1.col-md-1.col-md-1.col-md-1.col-md-1.col-md-1
.col-md-8.col-md-4
.col-md-4.col-md-4.col-md-4
.col-md-6.col-md-6
<div class="row">
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
</div>
<div class="row">
<div class="col-md-8">.col-md-8</div>
<div class="col-md-4">.col-md-4</div>
</div>
<div class="row">
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
</div>
<div class="row">
<div class="col-md-6">.col-md-6</div>
<div class="col-md-6">.col-md-6</div>
</div>
图标库选择
https://fontawesome.com/how-to-use/on-the-web/using-with/react
安装react-fontawesome
1 | npm i --save @fortawesome/fontawesome-svg-core |
使用
1 | import ReactDOM from 'react-dom' |
FileSearch组件开发

使用ctrl+shift+i可以打开控制台
默认最大化
win = new BrowserWindow({show: false}) win.maximize() win.show()默认全屏
win = new BrowserWindow({fullscreen: true})
FileList组件开发
使用Bootstrap的列表组件
markdown图标在fontawesome图标库的brands分类下
使用styled-components编写style
不同文件类型渲染不同
useKeyboward封装
TabList组件开发
需求
使用bootstrap nav-pills实现
编辑器
TinyMCE 、 Ueditor
支持预览模式
支持高亮显示不同内容
支持自定义工具栏
如果github某个库很久以前更新的,可以谷歌该库的fork,看看有没有fork并维护该项目的新仓库
我们这里使用EasyMDE的react封装,react-simplemde-editor
SimpleMDE需要设置key
代码重构
state设计原则
下面这种设计是不好的,将打开状态、激活状态、保存状态都存储在文件的字段身上。更新比较麻烦而且冗余。可以把打开状态,激活状态、保存状态分别用数组保存对应文件id。这样设计更好。同时对象数组也不好使,建议转换成Flattern State
1 | export interface IFile { |
Flatten State
- 解决对象数组冗余
- 数据处理更加方便
方法封装
1 | import { IFile, IFlattenFiles } from "../types"; |
文件操作和数据持久化
使用electron-store进行数据持久化
导入文件
添加上下文菜单
https://newsn.net/say/electron-context-menu.html
1 | useEffect(() => { |
hooks封装
1 | import { useEffect } from "react"; |
使用ref保存点击节点并且使得菜单只在文件区域出现
1 | import { useEffect, useRef } from "react"; |
通过点击的dom获取到点击的区域
由于使用的是原生Dom,监听鼠标右键点击事件,只能获取到点击的原生Dom,不能获取到点击的是哪一个File。因此需要使用data-*进一步处理
1、在FileList循环时挂接上信息
1 | <li |
2、封装方法冒泡获取指定类的父节点
1 | /** |
3、使用
1 | //添加上下文菜单 |
添加原生应用菜单
MenuItem的accelerator属性可以设置快捷键
可以为MenuItem指定role属性
windows和os快捷键不同

1 | 1、菜单封装到一个ts文件 |
设置窗口
云同步
初始化七牛云平台
1、注册、实名认证、登录
2、控制台中主要使用对象存储服务

3、新建桶(Bucket)可以把桶理解为对象存储的容器,系统已经自动分配了测试域名,每天访问限额10GB,30个工作日会自动回收该域名,如果要继续使用,需要绑定自己的域名



查看七牛云nodejs文档并封装

由于electron支持使用nodejs,因此这里使用服务端直传
封装QiniuManager
1 | //引入qiniu的sdk |
使用
1 | const path = require("path"); |
流Stream
1、类似管道,边读边写,不会占用太多内存,流有事件,可以监听都或者写的生命周期

使用axios进行下载文件,因为它支持nodejs
- 七牛云上传文件不刷新问题解决,在cdn中刷新该文件即可


自动同步和全部同步到云端
- 保存时自动同步到七牛云
- 打开文件时判断本地的updateTime与七牛云的putTime(上传时间),如果putTime>updateTime代表服务器的文件比较新,需要将该文件下载到本地并且更新到store中
- 删除、重命名时都需要对七牛云进行同步:注意操作时要先对七牛云文件进行操作,然后才修改本地的updateTime,确保本地updateTime大于putTime,这样可以对比文件版本,因为用户如果通过其他方式对七牛云的文件进行了修改,那么putTime就会更新(变大)
- 上传或者下载比较慢,可以使用loading
打包electron
1、安装electron-builder npm install electron-builder -D
2、首先对react工程build npm run build
打包出一个静态资源文件夹
3、更新main.ts中生产环境的加载地址
1 | const urlLocation = isDev |
4、在package.json中配置electron-builder打包配置
1 | { |
- 问题1:无法下载winCodeSign https://www.cnblogs.com/savokiss/p/9129956.html
打包:执行npm run dist即可,打包结果如下

打包体积优化


自动更新(不做了,没有服务器)
- token

使用LuckySheet
需要安装moment和引入Jq cdn
excel文件导入导出使用Luckyexcel
