# 回顾
上一节的我们完成了Electron
的初始化工作,即可以把一个web应用打包为exe/app,使得用户使用起来更方便
。
这一节我们就来讲讲在Electron里面的通信模式
。
由于这块资料比较少,官网虽有介绍,但是Demo不是很全,所以可能存在我理解不当的地方,还请各位指点一二。
# Electron的通信机制
我们知道,Electron要想开发一个桌面端的应用,那么必须能够和文件系统打交道。比如我这个markdown编辑器
,如果只是临时编辑,而不能落地到文件系统的话,那将毫无意义
。
但我们知道,JavaScript其实是不具备这样能力的,所以我们需要借助一门后端语言(nodejs)来帮我们完成文件系统相关的交互。
但有人可能会问了,你怎么一会react一会nodejs,给我整懵逼了都,是的,因为我一开始也是懵逼的。
我们把前端想象成View层即可。
那Electron里面的通信机制是什么样的呢,我这里简单介绍一下:
Electron会有一个ipcMain
或ipcRender
(我理解的是main是放到主线程的,render是放到渲染线程即前端的)来监听Js端或Nodejs端的消息,接着做对应的处理。我们以编辑文件为例,来看看他的流程:
- React读取文件输入框的值
- 点击保存按钮后,向ipcMain发送消息(每个消息都有一个自己的频道)
- 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);
});
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>
)
}
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;
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()
})
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给别人试用。