最近在开发一款桌面端应用,用到了Electron
和React
。
React
作为日常使用比较频繁的框架,这里就不详细说明了,这里主要是想通过几篇文章让大家快速上手Electron
以及与React
完美融合。
本篇是系列文章的第一篇,主要是给大家分享Electron
的一些概念,让大家对Electron
有一个初步的认知。
先来了解一下什么是Electron
吧,可能很多小伙伴还没有听过Electron
,相信很多小伙伴此时的表情是这样的:
看下官网[1]的自我介绍:
Electron
是一个可以使用 Web
技术如 JavaScript
、HTML
和 CSS
来创建跨平台原生桌面应用的框架。借助 Electron
,我们可以使用纯 JavaScript
来调用丰富的原生 APIs
。
Electron
用 web
页面作为它的 GUI
,而不是绑定了 GUI
库的 JavaScript
。它结合了 Chromium
、Node.js
和用于调用操作系统本地功能的 APIs
(如打开文件窗口、通知、图标等)。
上面这张图很好的说明了Electron
的强大之处。
正因如此,现在已经有很多由Electron
开发的应用,比如Atom
、Visual Studio Code
等。我们可以在Apps Built on Electron[2]看到所有由Electron
构建的项目。
快速开始
前面说了那么多废话,下面进入正题,带大家用五分钟(为什么是五分钟?我猜的 ? )的时间运行一个Electron
的Hello World
。
安装
这一步很简单:
代码语言:javascript复制npm install electron -g
第一个 Electron
应用
一个最简单的 Electron
应用目录结构如下:
hello-world/
├── package.json
├── main.js
└── index.html
package.json
的格式和 Node
的完全一致,并且那个被 main
字段声明的脚本文件是你的应用的启动脚本,它运行在主进程上。你应用里的 package.json
看起来应该像:
{
"name": "hello-world",
"version": "0.1.0",
"main": "main.js"
}
创建main.js
文件并添加如下代码:
const { app, BrowserWindow } = require("electron");
const isDev = require("electron-is-dev");
const path = require("path");
let mainWindow;
app.on("ready", () => {
mainWindow = new BrowserWindow({
width: 1024,
height: 680,
webPreferences: {
nodeIntegration: true,
// https://stackoverflow.com/questions/37884130/electron-remote-is-undefined
enableRemoteModule: true,
},
});
// https://www.electronjs.org/docs/api/browser-window#event-ready-to-show
// 在加载页面时,渲染进程第一次完成绘制时,如果窗口还没有被显示,渲染进程会发出 ready-to-show 事件 。 在此事件后显示窗口将没有视觉闪烁
mainWindow.once("ready-to-show", () => {
mainWindow.show();
});
const urlLocation = `file://${__dirname}/index.html`;
mainWindow.loadURL(urlLocation);
});
然后是index.html
文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Hello World!</title>
<style media="screen">
.version {
color: red;
}
</style>
</head>
<body>
<h1>Hi! 我是柯森!</h1>
</body>
</html>
到这里main.js
, index.html
和 package.json
这几个文件都有了。万事俱备,来运行这个项目。因为前面已经全局安装了electron
,所以我们可以使用 electron
命令来运行项目。在 hello-world/
目录里面运行下面的命令:
$ electron .
你会发现会弹出一个 electron
应用客户端,如图所示:
到这里,我们已经完成了一个最简单的electron
应用。
但你一定会对上面用到的一些api
有疑惑,下面我将带大家深入浅出的了解一下electron
的常用概念和api
。
相关概念
Electron
的进程分为主进程和渲染进程。在说这个之前,我觉得有必要先说一下进程和线程的概念。
进程和线程
这里参考的是廖雪峰老师关于进程和线程概念的阐述,我觉得说的清晰明了。
对于操作系统来说,一个任务就是一个进程(Process
),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word
就启动了一个Word
进程。
有些进程还不止同时干一件事,比如Word
,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread
)。
主进程和渲染进程
主进程
在 electron
里面,运行 package.json
里面 main
脚本的进程被称为主进程
。主进程控制整个应用的生命周期,在主进程中可以创建 Web
形式的 GUI
,而且整个 Node API
是内置其中。
渲染进程
由于 Electron
使用 Chromium
来展示页面,所以 Chromium
的多进程架构也被充分利用。每个 Electron
的页面都在运行着自己的进程,这样的进程我们称之为渲染进程
。
在一般浏览器中,网页通常会在沙盒环境
下运行,并且不允许访问原生资源。然而,Electron
用户拥有与底层操作系统直接交互的能力。
主进程与渲染进程的区别
主进程使用BrowserWindow
实例创建页面。每个BrowserWindow
实例都在自己的渲染进程里运行页面。当一个BrowserWindow
实例被销毁后,相应的渲染进程也会被终止。
主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的页面。
在 electron
中,页面不直接调用底层 APIs
,而是通过主进程进行调用。所以如果你想在网页里使用 GUI
操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI
操作。
在 electron
中,主进程和渲染进程的通信主要有以下几种方式:
- ipcMain、ipcRender
- Remote 模块
进程通信将稍后在下文详细介绍。
BrowserWindow 的创建
BrowserWindow
用于创建和控制浏览器窗口。像上面的hello-world
中:
mainWindow = new BrowserWindow({
width: 1024,
height: 680,
webPreferences: {
nodeIntegration: true,
// https://stackoverflow.com/questions/37884130/electron-remote-is-undefined
enableRemoteModule: true,
},
});
const urlLocation = `file://${__dirname}/index.html`;
mainWindow.loadURL(urlLocation);
创建了一个1024*680
的窗口,并通过loadURL
方法来加载了一个本地的html
文件。
这里一般会通过区分环境加载对应不同的文件。
进程间的通信
在计算机系统设计中,不同的进程间内存资源都是相互隔离的,因此进程间的数据交换,会使用进程间通讯方式达成。而不同于一般的原生应用开发,Electron
的渲染进程与主进程分别属于独立的进程中,而且进程间会存在频繁的数据交换,这时选择一个合理的进程间通讯方式显得尤为重要。下面是 Electron
中官方提供的进程间通讯方式:
window.postMessage,LocalStorage
在前端开发中,鉴于浏览器对本地数据有严格的访问限制,所以一般通过该两种方式进行窗口间的数据通讯,该方式同样适用于 Electron
开发中。然而因为 API
设计目的仅仅是为了前端窗口间简单的数据传输,大量以及频繁的数据通讯会导致应用结构松散,同时传输效率也值得怀疑。
使用IPC
进行通信
Electron
中提供了 ipcRender
、ipcMain
作为主进程以及渲染进程间通讯的桥梁,该方式属于 Electron
特有传输方式,不适用于其他前端开发场景。Electron
沿用 Chromium
中的 IPC
方式,不同于 socket
、http
等通讯方式,Chromium
使用的是命名管道 IPC
,能够提供更高的效率以及安全性。
主进程收发信息
详细参考
ipcMain
- 主进程接收渲染进程发送的信息
ipcMain.on("message", (e, msg) => {
console.log(msg);
});
- 主进程(主窗口)发送信息给渲染进程
mainWindow.webContents.send('message', { name: 'from the main by cosen' });
渲染进程收发信息
通过
ipcRenderer
发送或接收
- 渲染进程接收主进程发送的信息
ipcRenderer.on("message", (e, msg) => {
console.log(msg);
});
- 渲染进程发送信息给主进程
ipcRenderer.send("message", { name: "Cosen" });
使用remote
实现跨进程访问
remote
模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC
)的简便途径。
Electron
中, 与GUI
相关的模块(如 dialog
, menu
等)只存在于主进程,而不在渲染进程中 。为了能从渲染进程中使用它们,需要用ipc
模块来给主进程发送进程间消息。使用 remote
模块,可以调用主进程对象的方法,而无需显式地发送进程间消息。
总结
本小节我们大概的了解了Electron
的一些概念以及运行了一个入门的hello-world
程序。但这远远还不够,下一节我会讲一下如何将Electron
与React
完美融合,毕竟还是要更贴近业务的~
好了,不早了,我要去开启我的网易云时光了 ?
参考资料
[1]
官网: https://www.electronjs.org/
[2]
Apps Built on Electron: https://www.electronjs.org/apps
- END -