首先看一下窗口间需要传输的最大的数据体,就不全展示了,只看一下概貌,也就是指下文中的this.$store.state:
Electron在版本8的时候,类似这种的操作是稀松平常的:
var jsonObj = {
isSendConversation: true,
isCreatedGroup: true,
isAddGroupUser: true,
sendTitle: "添加群成员",
groupId: groupId,
activeUsers: groupMemberUserInfos,
storeState: this.$store.state,
};
ipcRenderer.send("show-ModalComponent-window", {
url: url,
source: jsonObj,
type: "sendUserCard",
dataKey: currentWindow.id,
});
但自从升级到Electron版本15以后,中间有一些断崖式的升级,ipcRenderer.send携带参数中已经不允许使用非结构化数据了,用以上的方式传递都会报错,因为数据中包含了大量的自定义Object类型,于是乎有人给了这样一个方案:
ipcRenderer.send("show-ModalComponent-window", {
url: url,
source: JSON.parse(JSON.stringify(jsonObj)),
type: "sendUserCard",
dataKey: currentWindow.id,
});
是的,你没看错,将数据用JSON.parse(JSON.stringify())包一下,这样最初确实是解决了问题,但仅限于对小型固定大小的需要转换的数据,要知道我目前开发的是一个聊天工具,以上数据中存了很多会话种消息,这些都是数量不固定的,伴随数据量增大,于是乎终于有一天爆了,报错:Invalid string length:
定位错误到这一行:
于是,就开始了各种的折腾历程:
1、 这些数据一部分是从本地sdk中获取的,但还有一些是处理中生成的,如果在打开的新页面中再从sdk获取,一是速度/性能上不允许,再者也不现实,于是放弃
2、 保存到本地再读取,这个io操作在速度/性能上也是大打折扣的,另外,还需要考虑实时保存变更的数据,也不可取
3、 保存到indexeddb再读取,这个比io操作会快一些,但实时保存变更数据的问题依然是道槛儿,再者,读取操作只能异步处理
4、 数据传到background后,在主进程中设置全局变量:
remote.setGlobal('sharedObject', { someProperty: 'someValue' });,
然后,在渲染进程中取出全局变量:
const sharedObject = remote.getGlobal('sharedObject');
但是问题来了,数据传输到主进程岂不是还避免不了JSON.parse(JSON.stringify())的包裹?于是也不靠谱。
不想使用JSON.parse(JSON.stringify())的原因有三,一是有长度限制,再一个数据量越大,转换的耗时也越大,三是最重要的,就是如果里面有object类型,这样的转换会破坏掉此数据格式。
5、幸好,浏览器支持使用shared worker,在不同页面间传递数据,这里说传递,不是共享,因为被传的数据和传递后的数据不是指向同一内存地址的,也就是说两者从此就不再同步了
以下就简单说明一下使用方式:
5.1 首先,在public文件夹中创建一个sharedWorker.js文件,内容如下:
let connectionCount = 0;
let peers = [];
onconnect = function(e) {
let port = e.ports[0];
connectionCount++;
peers.push({
id: connectionCount,
port: port
});
port.postMessage({
kind: "connect",
peerId: connectionCount
});
port.onmessage = function (e) {
if(e.data.kind == "close"){
const index = peers.findIndex(item => item.id == e.data.peerId);
if(index >= 0){
peers.splice(index, 1);
}
}else if(e.data.kind == "getCount"){
peers.forEach((peer) => {
// 只给请求者发送
if(peer.id == e.data.peerId){
peer.port.postMessage({kind: "getCount", count: peers.length});
}
});
}else if(e.data.kind == "getData"){
// 自己窗口以外的所有窗口广播
peers.filter(function (peer) {
return peer.id !== e.data.peerId;
}).forEach(function (peer) {
peer.port.postMessage(e.data);
});
}else if(e.data.kind == "sendData"){
// 向自己窗口以外的指定窗口单独发送
peers.filter(function (peer) {
return peer.id !== e.data.peerId;
}).forEach(function (peer) {
if(!!e.data.toPeerId && peer.id === e.data.toPeerId){
peer.port.postMessage(e.data);
}
});
}
};
}
5.2 在store中为各窗口定义相应的传输数据用变量,注意有的窗口需要为数据指定key值,以防使用同一变量,快速点击打开同样的多个窗口(比如类似微信的独立窗口)时造成数据混乱:
各窗口的入口js中这样定义,我是统一在store中定义的,注意dev和生产环境的路径指定不同:
再在store中定义此发送/接收用的方法:
方法有点儿大,主要关注e.data.kind == “connect”、e.data.kind == “getData”、e.data.kind == "sendData"的相关逻辑即可(e.data.kind == "getCount"是为了观察关闭窗口时有无未关闭的链接,防止造成内存泄露)
5.3 接下来就可以传输数据了,在ipcRenderer.send之前塞入数据:
经过主进程转发后,在新窗口的渲染进程中通过以上store中的方法发送并接收数据:
这样,全程避开了JSON.parse(JSON.stringify())的包裹,就可以尽情的在新窗口中使用传入的数据了。
再说一个重点,一般sharedWorker用来多窗口广播的操作比较多(包括广播给自己),但实验发现,如果打开了多个窗口,即时收到广播进行判断不做处理,接收广播的瞬间各窗口的内存也是有消耗的,所以以上代码考虑了指定窗口发送的方式进行传输。
5.4 最后,在窗口关闭时不要忘记关闭port,否则内存会暴涨的哦:
另外,还有一个前提很重要!!!一定是多页面多入口(每个页面有自己的html文件),才可以使用sharedWorker,否则,逻辑不报错,但事件就是会触发,我就入过此坑,切记!切记!