Cosen95 / blog

关注行业前沿,分享所见所学。持续输出优质文章 :rocket:
212 stars 15 forks source link

万物皆可快速上手之Electron(第一弹) #62

Open Cosen95 opened 3 years ago

Cosen95 commented 3 years ago

最近在开发一款桌面端应用,用到了ElectronReact

React作为日常使用比较频繁的框架,这里就不详细说明了,这里主要是想通过几篇文章让大家快速上手Electron以及与React完美融合。

本篇是系列文章的第一篇,主要是给大家分享Electron的一些概念,让大家对Electron有一个初步的认知。

先来了解一下什么是Electron吧,可能很多小伙伴还没有听过Electron,相信很多小伙伴此时的表情是这样的:

看下官网的自我介绍:

Electron 是一个可以使用 Web 技术如 JavaScriptHTMLCSS 来创建跨平台原生桌面应用的框架。借助 Electron,我们可以使用纯 JavaScript 来调用丰富的原生 APIs

Electronweb 页面作为它的 GUI,而不是绑定了 GUI 库的 JavaScript。它结合了 ChromiumNode.js 和用于调用操作系统本地功能的 APIs(如打开文件窗口、通知、图标等)。

上面这张图很好的说明了Electron的强大之处。

正因如此,现在已经有很多由Electron开发的应用,比如AtomVisual Studio Code等。我们可以在Apps Built on Electron看到所有由Electron构建的项目。

快速开始

前面说了那么多废话,下面进入正题,带大家用五分钟(为什么是五分钟?我猜的 🐶 )的时间运行一个ElectronHello World

安装

这一步很简单:

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.jsindex.htmlpackage.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 中,主进程和渲染进程的通信主要有以下几种方式:

进程通信将稍后在下文详细介绍。

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 中提供了 ipcRenderipcMain 作为主进程以及渲染进程间通讯的桥梁,该方式属于 Electron 特有传输方式,不适用于其他前端开发场景。Electron 沿用 Chromium 中的 IPC 方式,不同于 sockethttp 等通讯方式,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程序。但这远远还不够,下一节我会讲一下如何将ElectronReact完美融合,毕竟还是要更贴近业务的~

好了,不早了,我要去开启我的网易云时光了 🤖

❤️ 爱心三连击

1.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

2.关注公众号前端森林,定期为你推送新鲜干货好文。

3.特殊阶段,带好口罩,做好个人防护。