这一篇就主要讲一讲第一次制作electron桌面应用的具体过程以及踩坑。
初次制作主要用的是electron+react的路线,react负责渲染,如果使用vue的话思路大同小异,毕竟框架仅负责渲染进程。
首先我用一个已经完成的react项目进行测试,在react项目的package.json中添加“homepage”:“/.”,然后进行build,之后在build文件夹中,安装electron,打包构建应用,启动桌面应用,react项目正常显示。这只是个小测试,实际用途不大。第一点是,在package.json中添加“homepage”:“/.”之后,build中的index.html可以直接访问并显示项目,引入electron仅仅只是给他套了一个桌面应用的壳;第二点是,构建的应用的所有功能实际上是使用react在前端进行实现的,它与本地资源、环境没有任何关联与操作,所以只能算作一次类似helloworld的操作。
我的第一个electron应用是个数据提取工具,我做了如下的规划:
1、主进程,使用node语言,负责本地资源的调用以及后端逻辑的处理;
2、渲染进程,使用react框架,仅负责前端的渲染;
3、数据资源使用sqlite,本地调用快捷方便,同时保证数据安全。
第一步首先用creat-react-app构建react项目,然后在react项目中加入electron的主文件main.js。
构建窗口,在这里,我就不展示main.js的构建窗口的代码了,官方文档写的很清楚,网上也可以查询到。不过在这里说一下第一个小坑,在构建mainWindows的时候,webPreferences中的配置参数,nodeIntegration与 preload两个参数实际上是冲突的,一旦你配置了预加载preload.js文件之后,nodeIntegration:true的配置就会失效。如果你只是简单的使用html页面来负责渲染,可以开启nodeIntegration,会比较方便,如果使用react或者Vue框架的话,建议使用preload配置。此外,还有一个建议,将electron 的main.js文件以及预加载preload.js文件都放在react项目的public文件夹中,这样在构建 react项目时,两个文件就会被直接引入到build文件中,package.json 中添加"main": "public/main.js",便于开发过程中的测试。
//关于node模式和预加载文件的设置
const mainWindow = new BrowserWindow({
width: 800,
height: 1000,
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js')
}
})
接下来,就是本次制作最大的难点,主线程与渲染线程之间的沟通。
根据官方文档的说明,主线程与渲染线程的沟通主要是通过ipcMain与ipcRenderer进行信息传递的:
//以下代码是写在main.js中
const { BrowserWindow,ipcMain } = require('electron')
function createWindow(
const mainWindow = new BrowserWindow()
//主进程发送信号“message”,传递参数a,a可以是string,array,object,下同
mainWindow.webContents.send('message',a)
//主进程监听到信号“message”,终端打印传递来的参数arg
ipcMain.on('message', (event, arg) => {
console.log(arg)})
)
//以下代码是在渲染部分
//渲染进程发送信号“message”,传递参数a
ipcRenderer.send('message',a);
//渲染进程监听到信号“message”,终端打印传递来的参数arg
ipcRenderer.on('message',(event,arg)=>{
console.log(arg)})
});
以上就在主进程与渲染进程形成闭环,进行信息的传递以及函数的调用。主进程的监听与发送相对简单,按照官方文档写在main.js中即可,但是想在react中调用ipcRenderer,实现的过程颇为复杂。
我首先在react中尝试使用
const {ipcRenderer } = require('electron');
react报错。
然后尝试改为import的方式:
import {ipcRenderer } from 'electron'
依旧react报错。
只能继续寻找解决方案。我在网上搜索到了一个方法,然后进行尝试。
const electron = window.require('electron');
const {ipcRenderer} = electron;
依旧报错。但是这个方法给了我灵感,调用window,或许有想不到的收获。
查找官方文档,然后研究了github中electron-react-boilerplate模板,在官网上有这么一段代码引起了我的注意(如下所示,在electron-react-boilerplate上也有类似的代码),我就在preload.js加入了该段代码进行测试,而后在react中加入console.log(window)的语句。
preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})
npm start启动react,但结果依然不尽如人意,并没有什么特别的发现。就在这时,我突然意识到一个问题,单单调用react,electron实际并未介入其中。只能将react打包,然后在build文件中测试electron,查看后台,我在输出window中看到了electron和下一层的setTitle,豁然开朗。然后我就在react的代码中加入了如下代码,再次打包,测试electron,在主进程可以监听“set-title”可以获取到a参数,至此,主进程与react负责的渲染进程的通信打通了。
window.electron.setTitle(a)// a为传递的参数
而后又遇到一个问题,就是在开发的时候打包进行测试,有点麻烦了。于是我参考了网上的一个方法,将主进程中的mainWindow.loadFile("index.html") 改为
mainWindow.loadURL("127.0.0.1:3000") //127.0.0.1:3000 react的测试地址
然后先用npm start启动react ,注意,由于react框架中引入了electron模块的内容,所以在单独启用react项目时,是不会显示任何东西的,会报错“ Cannot read properties of undefined (reading '函数名')”,在preload中定义的函数由于没有启动electron,会显示未定义。然后在用electron . 启动electron的测试,在应用中页面呈现,功能加载,并且修改代码之后保存,页面也会重新加载,便于开发维护。
初次开发的整体思路大致如此,最后,再写一个小坑,解决了之后可以让代码更方便简洁。关于ipcRenderer函数的写法,如ipcRenderer.send()函数有两个参数,第一个是信号的名称,第二个是传递的参数,preload.js作如下修改:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
setTitle: (message,obj) => ipcRenderer.send(message, obj)
recieveTitle:(message,obj) => ipcRenderer.on(message, obj)
})
这样在react中使用ipcRenderer的时候,只需要调用setTitle()和recieveTitle()两个参数就可以了,通过不同的信号message实现不同的传参调用。
软件制作的难点就是以上这些了,解决了这些问题,其他的就是常规的前后端开发。最后再上一次软件截图: