最近在搞Electron-vue
的项目,由于之前没做过这种客户端的开发,所以在开发的过程中一直磕磕绊绊,碰到各种异常的情况,而且网上的资料很少,很多问题都找不到解决办法,而且很多博客都是重复的。一篇博客被很多人转载,而且有的博主会把自己的一片文章发布到不同的平台上,这就导致在搜索的时候,看到很多篇一样的文章,而且都是不能解决问题的,就很烦。
好了闲话少说,这里只是吐槽一下网上千篇一律的博客。我所碰到的问题是这样的。
我做的客户端挺简单的,只有登录页,主页面以及修改密码页,所以页面要展示的东西不是很多,由于主页面没有什么可以展示的,所以几乎所有的操作都会放到托盘中处理,这样可以减少页面由于没有内容而导致的空白太多。就像下面这样简单地浮条,类似于搜狗输入法似的。而且登录后默认会隐藏到托盘中,用户点击托盘图标才会展示。
基于上面的描述,我会把主页面的功能都集中在托盘中,如下。
修改密码会从主页面切换到修改页面,
注销功能会从主页面切换到登录页面,
退出功能是直接退出销毁窗口。
其中的修改密码和注销功能,都是从主进程
向渲染进程
中发送请求,渲染进程
收到请求进行页面跳转。
如果是主进程主动发起事件,那就必须使用窗口发送事件请求mainWindow.webContents.send
,然后在渲染进程中监听事件,做一些逻辑处理。无论是主进程主动发送事件还是渲染进程主动发送,然后主进程监听到再次发送事件。渲染进程都需要使用on
或者once
去做监听。
once: 建立一次性的监听,当监听事件触发执行后,便会销毁该事件监听。
on: 建立永久性监听,即会持续监听该事件的触发,这样可以在程序的生命周期中不断地监听事件触发。
好了,知道上面的两种监听方式的区别,我们来看看实际操作会碰到什么问题。
我是在App.vue
中用once
去监听注销事件,当注销成功后,返回登录页,然后注销事件
监听就会被销毁。我们虽然返回到登录页,但是整个APP
的生命周期还是在的,并没有重新加载整个APP
,当然我们也不会让他去重新加载整个应用。
那么就会出现一个问题,我在第一次注销时,是能够注销成功的,注销成功会销毁注销的监听事件,但是等你重新登录后,APP不会重新加载,导致你的注销事件的监听并没有重新建立,那么当你再次点击注销功能时,发现主进程发送的注销事件,在渲染进程中没法监听了。因为你上次上次注销已经销毁了。
猜想,既然使用once
只是一次性的,在使用完就会被销毁,那么我使用on
是不是就可以建立一直监听了,那么在多次重复登录注销,都不会错误了?
对,你想的不错,确实是这样,它会一直处于监听状态,而不会因为销毁而导致无法再次注销。
我们来看一下官方给出的on
的解释:
为什么要强调这一点呢?
监听channel,当有新消息到达时,使用listener调用listener
,注意这个新消息
,这个消息指的是什么呢?
有的可能会认为那肯定是ipcRenderer.on
的那个事件的新消息了?如果你是用的是我这种通过child_process
的spawn/exec
去跟子进程通信,那么你所有的写操作的结果都会更新到stdin
中,这就导致如果你使用on
建立长监听,有信息更新就会去调用他的callback
函数。
可能有人没理解什么意思。比如我现在有两个操作,一个修改操作,一个注销操作,两个操作都是会通过stdin
进行写操作,那么如果我给注销功能加了个长监听,那么在我在修改密码的时候进行的写操作会更新到stdin
中,导致信息更新,那么也就会被注销的监听事件给监听到,致在修改密码之后就会在调用修改密码的callback
函数之后再调用注销的callback
函数,从而导致逻辑错误。
所以综上所述无论你使用on
或者once
在App.vue
中做监听事件都行不通。
于是我就尝试着在其他模块去执行once
,既然App.vue
始终只是被加载一次,那我就在其他模块,比如主页面去做这个操作行不行呢?因为无论是登录后还是修改密码后,都会返回到主页面,那么在注销之后,注销事件被销毁了,登陆之后又重新添加一个注销事件
,然后再注销就不会找不到监听事件了?
但事实并非如此,我们知道如果在主页面去添加监听事件,那么我们只能在生命周期函数中去添加,那么在加载这个模块的时候就会去添加监听事件,我是放到vue
的created
方法中的。你可想而知,当你反复的从修改页面调到主页面的时候,主页面的created
总会被执行,那么就导致会反复创建注销
的监听函数。那么你在做修改操作的时候,还是会去触发监听事件,多个once
其实就相当于on
了。
我也曾尝试着在App.vue
中使用once
去做监听,然后注销的时候去重新加载渲染整个APP,就是使用nextTick
的方式,然后给router-view
添加v-if
去控制显示隐藏,然后通过事件触发比如登陆时间去重新reload
,发现输出打印,并没有重新执行created
生命周期函数,导致无法从新创建一个新的注销监听事件
。
基于以上的分析觉得如果单独去处理on
和once
确实达不到预期的结果,总结一下就是:
- 在
APP
中,渲染进程使用once
监听,则注销登陆时会导致,监听过一次就会销毁监听事件,但是APP
应用并没有退出,没办法重新加载APP
导致没有建立新的监听事件,所以无法触发第二次注销事件的监听,无法注销。- 如果在
APP
中使用对渲染进程使用on
监听,就会开启持续监听状态,该监听会导致其他操作在执行stdin.write
时,仍旧会触发登出事件
,就会导致修改密码之后触发登出事件
,然后程序出现异常。- 如果不在
APP
中对渲染进程监听,即在其他功能模块使用once
监听,则进行其他操作时,返回该应用时,就会再次注册该退出
事件,导致其他操作时仍会监听到write
操作,导致注销
仍会触发执行,导致程序异常。
思考了一下,还是要回到怎样才能避免在执行其他操作的时候不会触发注销的callback
函数,或者说是即便触发了,我们也可以使用一些小技巧组织send
的发送,这样在渲染进程中就无法监听到主进程的事件,也就不会走我们的注销逻辑,这样就可以了。
按照这种思考方法我们就得出这样的结论:在主进程中定义标志位默认为true,此时可以执行注销操作。然后其他的操作执行write的时候,将该标志位置为false。当结果执行完后,将该标志位回档即置为true,那么再做完其他操作时返回到主页面的时候,不会影响正常的注销操作
。
当我们使用一种新的技术做开发的时候,文档资料远远不能满足我们的开发,因为我们的业务可能千变万化,涉及的复杂度也不会是官方提供的那么操作。需要我们在看懂API
的同时,有自己的结局问题的办法思路,去在日常开发时,利用一些小技巧去实现我们的功能。
// 登出
function logout() {
child.stdin.write('{"type":"logout"}\n')
child.stdout.on('data', (data) => {
if(isNotPassword){
mainWindow.webContents.send('logout', returnString(data))
isNotPassword = true
}
})
}
该篇文章文字较多,贴图代码较少,因为是在解决问题之后写的博客,之前错误代码就没有恢复,所以就没有粘贴出来。如果有疑问欢迎留言。