electron ajax路径,Electron踩坑记

最近一直在做Electron的项目,随着项目的上线,在此做一点总结。本文主要记录一些坑点,整个Electron的搭建流程有很多文章说的非常好了,就不赘述了。

主要包括

关于主进程和渲染进程通信的痛点

持久化数据的方案选型

打包后文件目录的访问权限

关于主进程和渲染进程通信的痛点

先看一下官方的说明

Electron为主进程( main process)和渲染器进程(renderer processes)通信提供了多种实现方式,如可以使用ipcRenderer 和 ipcMain模块发送消息,使用 remote模块进行RPC方式通信

这里提到了两种通信方式。前者需要通过事件注册发布的方式实现数据传递,后者屏蔽了通信的细节,可以像调用普通对象一样共享数据。

例如获取列表数据,使用事件注册发布的方式代码大概是这样

// 在主进程中

const { ipcMain } = require('electron')

ipcMain.on('getList', (event, arg) => {

//获取数据的代码

//实际中可能类似service.getList(arg)

event.sender.send('getList-done', 'list data');

})

复制代码// 在渲染进程中

const { ipcRenderer } = require('electron')

ipcRenderer.on('getList-done', (event, arg) => {

console.log(arg); // 输出 'list data'

})

ipcRenderer.send('getList', 'list args')

//组件销毁后移除这个监听

ipcRenderer.removeAllListeners('getList-done')

//或者ipcRenderer.removeListener移除指定的监听

复制代码

而使用remote模块,代码大概是这样

// 在主进程中

const { app } = require('electron')

// 封装的一个关于数据库请求的service

app.service = new Service()

复制代码// 在渲染进程中

const {remote} = require('electron')

// 通过remote模块直接调用getList

const res = remote.app.service.getList()

复制代码

通过对比可以看出,使用前者会导致很多的样板代码,而使用remote模块则非常方便。然而看似美好的remote模块在实际使用中暴露了很多问题。getList查询在主进程耗时10ms,通过remote模块拿到数据却需要将近100ms。

这一度令我不能理解,在一步步调试后发现,每次访问对象都会进入metaToValue的方法,包括对象上属性的访问。此外还会造成渲染进程卡顿,查阅文档了解到由于remote的本质是发送同步间进程消息,会阻塞主进程,换言之如果一个查询比较耗时,则用户界面会直接卡死甚至出现未响应的情况。

关于这里的问题,我理解有限,大家有兴趣的可以移步荒山大佬的文章,里面有对remote模块的详细分析。

由于remote模块天生的缺陷,还是只能使用事件注册的形式去进行进程间通信。但是实际项目中需要很频繁的和主进程通信(访问本地数据库),如果为每一个方法都注册事件,实在过于繁琐。下面给出我的一个封装方案:

// 在主进程中

// 先提前实例化好service挂到app对象上

ipcMain.on("queryData", async function (event, args) {

const {

serviceName,

serviceMethod,

serviceArg,

key

} = args;

const res = await app[serviceName][serviceMethod](...serviceArg)

event.sender.send("queryFinish", {

key,

data: res

})

})

复制代码type ipcParam = {

serviceName: string;

serviceMethod: string;

serviceArg: any | null;

};

export function ipcHelper(args: ipcParam) {

//生成一个唯一id

const key = getLongId();

let listener: any = null;

const bindEvent = (r: any) => {

return async (event: any, arg: any) => {

//通过key来唯一确定收发消息的双方

if (arg.key === key) {

r(arg.data);

ipcRenderer.removeListener("queryFinish", listener);

}

};

};

return new Promise((r, j) => {

ipcRenderer.send("queryData", {

...args,

key

});

listener = bindEvent(r);

ipcRenderer.on("queryFinish", listener);

});

}

// 调用

export function getList(...params: any):Promise{

return ipcHelper({

serviceName:'testService',

serviceMethod: "getList",

serviceArg: params

});

}

// 拿到结果

const res = await getList('参数')

...

复制代码

经过对事件的promise化,在渲染进程,也就是实际的页面当中,就可以像调用Ajax请求一样方便地请求本地数据了。

持久化数据的方案选型

由于项目本身的一个重点就是处理本地数据,因此本地数据的持久化存储就成了不得不面对的问题。我们组内当初有两个方案:

indexedDB浏览器内置的数据库,文档型数据库

sqlite成熟的内嵌型数据库,关系型数据库

项目一期决定使用indexedDB,出于以下几点考虑

浏览器内置,无需考虑安装打包可能遇到的问题

完全通过渲染进程,无需和主进程进行通信

文档型数据库,不需要额外学习sql语法,门槛较低

但经过一段时间的测试和开发,发现以下弊端

在数据量超过10w时性能严重下降

多表联合查询受限,往往需要把数据全部加载到内存中操作

调试困难,浏览器内置界面只能够一页页翻看数据,没有找到配套的工具,无法快速查询数据

项目二期用sqlite3替换,有以下优点

性能非常优秀,可以支撑百万数据量级的查询和插入

相关生态丰富,有成熟的第三方工具(我们使用的navicat)可以快速定位问题

关于indexedDB和sqlite3,分别使用了Dexie和Knex来辅助数据的操作。这部分有空的话准备单独写一篇来介绍,这方面的中文资料比较欠缺,项目过程中也踩了不少坑。

打包后文件目录的访问权限

由于使用了sqlite3,因此需要对文件进行生成和访问。在开发模式下很顺利,但打包后遇到了找不到路径的问题。

//使用__dirname作为根路径创建文件在开发环境下没有问题

path.join(__dirname)

复制代码

原因在于electron-builder打包后会把文件都打进了 .asar 包中,__dirname作为js执行的绝对路径\resources\app.asar\dist\electron,

显然位于 .asar包中,参考官网的解释

档案文件中的内容不可更改,所以 Node APIs 里那些会修改文件的方法在使用asar 归档文件时都无法正常工作.

要想解决这个问题,有两个方法

把文件的生成目录指向.asar同级或者更上层的目录,例如path.join(__dirname, '/../..')

package.json中build下设置"extraResources": { //把需要访问的文件移动到外层目录

"from": "template",

"to": "temp"

},

复制代码

总结

大概就这么多,欢迎大家指正和讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值