Electron实战笔记之与Web端与操作系统交互.md

2022/6/13 Electron

# 回顾

上一节的我们完成了Electron的初始化工作,即可以把一个web应用打包为exe/app,使得用户使用起来更方便

这一节我们就来讲讲在Electron里面的通信模式

由于这块资料比较少,官网虽有介绍,但是Demo不是很全,所以可能存在我理解不当的地方,还请各位指点一二。

# Electron的通信机制

我们知道,Electron要想开发一个桌面端的应用,那么必须能够和文件系统打交道。比如我这个markdown编辑器,如果只是临时编辑,而不能落地到文件系统的话,那将毫无意义

但我们知道,JavaScript其实是不具备这样能力的,所以我们需要借助一门后端语言(nodejs)来帮我们完成文件系统相关的交互。

但有人可能会问了,你怎么一会react一会nodejs,给我整懵逼了都,是的,因为我一开始也是懵逼的。

我们把前端想象成View层即可。

那Electron里面的通信机制是什么样的呢,我这里简单介绍一下:

Electron会有一个ipcMainipcRender(我理解的是main是放到主线程的,render是放到渲染线程即前端的)来监听Js端或Nodejs端的消息,接着做对应的处理。我们以编辑文件为例,来看看他的流程:

  1. React读取文件输入框的值
  2. 点击保存按钮后,向ipcMain发送消息(每个消息都有一个自己的频道)
  3. nodejs端也有一个线程在监听一些消息,一旦收到保存的消息事件,他就会执行对应的方法。

# 实现一个Demo

修改main.js

// 从electron获取ipcMain事件
const {ipcMain} = require('electron')
const fs = require('fs')
// 用nodejs写一个保存文件的方法
ipcMain.on('update-file', (event, data) => {
fs.writeFileSync(data.file, data.value);
});

1
2
3
4
5
6
7
8

上述代码意思是,监听update-file事件,一旦收到该事件,则读取对应data的file字段作为文件名,data.value作为文件的内容。

# 最后会给出完整demo。

前端部分:

import React from 'react';

export default () => {
  const [value, setValue] = useState('');

  return (
      <Input value={value} onChange={e=>{setValue(e.target.value)}}/>
      <button onClick={() => {
          window.ipcRender.send("update-file", {file: "文件路径", value: value})
      }}>保存</button>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12

由于文件路径这块我们没有具体的选项,我们直接写死,大概流程就是这样。点击按钮调用ipcRender的发送事件,main.js里面有方法监听此事件,转而去操作nodejs。

# 改动preload.js

上述代码可能有人问,window.ipcRender是undefined,确实,我也因为这个卡了好久,踩了不少坑。那是因为我们的preload.js没有配置完善。

修改preload.js

const {
  ipcRenderer
} = require("electron");

window.ipcRenderer = ipcRenderer;

1
2
3
4
5
6

preload.js会在electron开始的时候加载ipcRender,把他赋予给window,后面我们在react就可以用这个方法了。

接着我们要去调整一下main.js

# 继续修改main.js

我们在create window的时候,一定要注意这几个参数,其他的都是默认的数据,如果你不想叫preload.js,也可以叫别的,记得是在根目录哈。

# 完整代码如下

const {app, BrowserWindow, ipcMain, dialog, Menu, globalShortcut} = require('electron')
const path = require('path')
const fs = require('fs')


const createWindow = () => {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      // contextIsolation: false,
      enableRemoteModule: true,
      preload: path.join(__dirname, 'preload.js'),
    }
  })

  /*隐藏electron创听的菜单栏*/
  Menu.setApplicationMenu(null)

  // 加载 index.html
  // mainWindow.loadFile(path.join(__dirname, '../build/index.html'))

  // mainWindow.loadURL(url.format({
  //     pathname: path.join(__dirname, './build/index.html'), // 注意这里修改
  //     protocol: 'file:',
  //     slashes: true
  // }))
  mainWindow.loadURL("http://localhost:8000")
  mainWindow.webContents.openDevTools()
  // mainWindow.loadFile(path.join(__dirname, './poraBuild/index.html'))
  return mainWindow;


// 打开开发工具
  // mainWindow.webContents.openDevTools()
}

app.whenReady().then(() => {
  const win = createWindow()

  var currentPath;

  app.on('activate', () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })

  ipcMain.on('update-file', (event, data) => {
    fs.writeFileSync(data.file, data.value, 'utf8');
  });


})


// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})
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
57
58
59
60
61
62
63
64
65

下一节我们可以聊聊怎么打包成exe给别人试用。