希沃ENOW大前端
公司官网:CVTE(广州视源股份)
团队:CVTE旗下未来教育希沃软件平台中心enow团队
本文作者:
前言
本系列共有以下几个章节:
主进程与渲染进程的两情相悦
渲染进程与渲染进程的搭桥牵线
定情信物传声筒port
您此次阅读的是第三章节:定情信物传声筒port。
注:以上所有文章都被归档到: https://github.com/LinDaiDai/niubility-coding-js 中 ,案例都上传至:https://github.com/LinDaiDai/electron-ipc-example ,欢迎 Star,感谢 Star。
大纲
在之前的文章中,我们主要介绍了 ipcMain
和 ipcRenderer
是如何实现主进程与渲染进程、以及渲染进程与渲染进程进行通信的。大家不难发现,之前介绍的方式都非常依赖主进程,特别是渲染进程之间的通信,每次都需要主进程这个中间人来传话,难道就没有什么更简单点的方式吗?
咦,这还真有一个,那就是利用 MessagePort
,通俗易懂的翻译过来:消息端口...(啪,就你英语好是吧)
大家别急,还不知道是啥玩意的话,让我来给大家介绍一下。用个小故事来简单举个例子哈:
渲染进程一(某先生)和渲染进程二(某女士)通过媒人介绍认识,由于男俊女美且三观相符,很快两人就看对眼坠入爱河了,但是奈何工作地点不在一起,不得不异地。这刚认识还处于暧昧期的俩年轻人咋能忍住不联系呢。于是互相交换了手机号加上了wx,相亲结束后,每日通过手机互相联系,增进感情,不日,便确定关系,订婚,结婚,买房,生娃......停停停,给我回来。
咳咳咳,故事呢,其实到 "每日通过手机互相联系"
就结束了哈,后面自行脑补。在上面这则小故事中,媒人就是主进程,渲染进程一和二在最初,会通过主进程进行一个交换 port
的过程,后续都通过 port
来进行通信,不再依赖主进程了。
乍一看,是不是觉得这种通信方式比前面介绍的那些靠谱多了,而且还不需要通过主进程中继的性能开销。
讲完了故事,聊了个大概,让我们来看看本篇文章大纲吧:
MessagePort
的基础用法
主进程与渲染进程通信案例分析
渲染进程与渲染进程案例预告
1. MessagePort
的基础用法
1.1 如何创建 MessagePort
首先还是得先来看看 MessagePort
的基础用法。 MessagePort
对象的创建依赖于 MessageChannel
类:
const channel = new MessageChannel();const port1 = channel.port1const port2 = channel.port2// 或者简写为:const { port1, port2 } = new MessageChannel();
实例化 MessageChannel
类之后,就产生了两个 port
: port1 和 port2
。这两个 port
就是 MessagePort
对象。它就是我们上面那则故事提到的,可以用于两个进程之间进行长期通信的关键所在。
举个小例子,假设现在:
渲染进程一有了 port1
渲染进程二有了 port2
那么现在这两个进程就可以通过 port.onmessage
和 port.postMessage
来收发彼此间的消息了:
// 渲染进程一:port1.onmessage = (event) => { console.log('received result:', event.data)};port1.postMessage('我是渲染进程一发送的消息');// 渲染进程二:port2.onmessage = (event) => { console.log('received result:', event.data)};port2.postMessage('我是渲染进程二发送的消息');
只要 port1
和 port2
一直都存在,它们就可以进行持久通信,怎么样,是不是很 niubility
。
OKK,那么现在如果是在渲染进程一创建的这两个 port
,关键就是如何把 port2
给到另一个渲染进程二了。也就涉及到了 MessagePort
的传递。
1.2 ipcRenderer.postMessage()
说到 MessagePort
的传递就得谈到 ipcRenderer
对象的 postMessage
方法了。因为 MessagePort
对象就是依靠它来传递。 没错,此时应该有小伙伴可能想起来了,我们平常网页上的 window
对象也有一个 postMessage
方法,这两者之间其实挺像的,只不过呢,是在不同的通道上。
portMessage
它的参数如下:
ipcRenderer.postMessage(channle, message, [transfer])
channel
String:事件名
message
any:要传递的消息
transfer
MessagePort[] (optional):0个或多个 MessagePort
对象。
前两个好理解,其实和 ipcRenderer
的其它方法差不多,事件,以及传递的消息。第三个参数有些特别,它是一个数组,其中可以传递 MessagePort
对象。这里需要注意,别看第三个参数标记的是 optional
,但其实它也是需要传递的,如果你不需要传 MessagePort
对象,那么就需要定义一个空数组,否则就会报错啦。
另外,在之前的文章中,我们还有用到 ipcRenderer
的其它方法: send
、 invoke
、 sendSync
,这三种方法主进程都是可以给渲染进程传递返回结果的,比如:
// render.jsconst replyMessage = await ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息');console.log('replyMessage', replyMessage); // "我是主进程返回的消息"
postMessage
的第二个参数也可以发送消息,那它是否也可以当成 send
或者 invoke
来用呢?这里我测试了一下,发现主进程那边不论是用 event.reply
还是用 event.returnValue
都不行,看来,官方还是希望我们遵循: ”什么样的API就做什么样的事”
,而 ipcRenderer.postMessage
,他的主要职责就是用来发送 MessagePort
的。
并且! ipcRenderer.postMessage
只能通过 ipcMain.on
来接收到, ipcRenderer.on
是接收不到的!
这样的话,看来如果我们要将某个 port
从一个渲染进程给到另一个渲染进程还是得依靠主进程了,需要它这个 媒人
来从中做媒。但问题不大,一旦这两人连接上了,就不再需要媒人了。
同时我们发现,通过这种方式我们也可以实现渲染进程与主进程之间的互相通信了,主进程在收到 port
的时候,如果不给其他人,自己用来和渲染进程通信也可以呀。
2. 主进程与渲染进程通信案例分析
好嘞,扯了这么多,让我们先写个小 demo,来看看通过 MessagePort
主进程与渲染进程是如何通信的吧。
和之前一样,让我们确定下要做什么事:
在某个时机,渲染进程创建了两个 port 并将其中一个(名为 port1)发送给了主进程
渲染进程这边的另一个 port2 绑定监听事件
主进程接收到 port1 并将它保存下来,同时也绑定监听事件
在另一个时机渲染进程通过 port2 给主进程发送消息
下面是 demo 的时序图:
第一步、调整目录结构
由于这个案例说的是渲染进程与主进程的通信,让我们基于之前的分支 example-3 再新建一个 example-4,同时删除我们不用的窗口2,此时目录结构变为:
第二步、渲染进程提供生成 MessagePort 并发送给主进程的能力
之前提到了,做的第一件事:
在某个时机,渲染进程创建了两个 port 并将其中一个(名为 port1)发送给了主进程
这里的某个时机,我们就在页面上定义两个按钮吧:
点第一个按钮创建并发送 port
点第二个按钮给主进程发送消息
// window-one/index.html<body> <h1>Window One</h1> <button onclick="sendPortToMain()">窗口1 postMessage 给主进程发送消息端口 port1</button> <button onclick="sendMessageToMain()">窗口1 通过 port2 给主进程发送消息</button> <script src="./renderer.js"></script></body>
对应的渲染进程的代码:
// window-one/render.jsconst { ipcRenderer } = require('electron')let portToMainfunction sendPortToMain() { // 1、创建一对 port const { port1, port2 } = new MessageChannel() // 2、给主进程传输消息端口 por1 ipcRenderer.postMessage( 'render-post-message-to-main', '我是渲染进程一通过 ipcRenderer.postMessage 发送过来的', [port1], ) // 3、把 port2 赋值给 portToMain,方便其他模块获取 portToMain = port2 // 4、port2 绑定事件监听,之后主进程发送的消息都会在这里接收到 portToMain.onmessage = (event) => { const data = event.data console.log('[Renderer receive]message', data) }}function sendMessageToMain() { portToMain.postMessage('我是渲染进程一通过传声筒 port 发送过来的')}
在上面代码中,点击第一个按钮执行 sendPortToMain
方法,其中创建了一堆 port
,并将其中一个通过 ipcRenderer.postMessage
发送给了主进程,同时设置监听。
点击第二个按钮,到时候会执行 sendMessageToMain
方法,就可以利用 port
进行通信了。
第三步、主进程提供接收 port 和设置监听的能力
在第二步中,渲染进程发送了 port
给主进程,那么主进程这边肯定要设置一个地方去接收,接收后同时也要保证它和渲染进程后续能持续通信。
那么只需要如下处理:
// main/ipc.jsconst { ipcMain } = require('electron')// 1、主进程监听一个事件,渲染进程想要发送 port 的话,就能在这里获取到ipcMain.on('render-post-message-to-main', (event, params) => { console.log('[Main receive]render-post-message-to-main', params) // 2、获取到 port1 const port1 = event.ports[0] // 3、需要调用一下 port1 的 start() port1.start() // 4、port1 绑定事件监听,之后渲染进程一发送的消息都会在这里接收到 port1.on('message', (event) => { const data = event.data console.log('[Main receive]message', data) port1.postMessage('我是主进程通过 port 回复的消息') })})
(记得将 main/ipc.js
在主进程中引用一下哦)
// main/index.jsconst ipc = require('./ipc')// ...其他代码
在上面的代码中,我们先用 ipcMain
保证能接收到渲染进程发送过来的 port
,再调用 port1.start()
,然后给它绑定 message
事件,之后渲染进程一发送过来消息都能接收到,也能通过 port1
给渲染进程一发。
第四步、效果演示
一切代码准备就绪,让我们启动项目来看看效果。
分别点击窗口中的第一个和第二个按钮,能够看到主进程和渲染进程的打印日志:
(终端里主进程的打印中文会乱码,还请理解…)
3. 渲染进程与渲染进程案例预告
在看完了上面的案例之后,相信你对于这种用 postMessage
进行窗口间通信的方式有了一些了解。实际的开发场景中,我们可能还会进行渲染进程与渲染进程间的通信,甚至是同一个进程内部之间的通信。在后面的文章中,我们会介绍如何通过 postMessage
来实现一个比较通用的 Electron ipc
通信的库,你也可以先利用上面的知识自己尝试着看看可以如何去写,敬请期待哦。
后语
这篇文章就介绍到这里。通过这三个章节的介绍,相信你对 ipc
大致的通信都有所了解了吧。希望在实际的开发中能够帮助到你。
喜欢「霖呆呆」的小伙还希望可以关注霖呆呆的公众号 LinDaiDai
。
我会不定时的更新一些前端方面的知识内容以及自己的原创文章?
你的鼓励就是我持续创作的主要动力 ?.
原文:https://juejin.cn/post/7103689764917755940