文章来源于公众号@LinDaiDai,https://mp.weixin.qq.com/s/myZPSlgDtds1ozmKIy64mA

《大概是全网最详细的Electron ipc 讲解(一)——主进程与渲染进程的两情相悦》,

《大概是全网最详细的Electron ipc 讲解(二)——渲染进程与渲染进程的搭桥牵线》

同时收录于小程序《互联网小兵》,

electron什么时候出(大概是全网最详细的Electron)(1)

技术人小程序,涵盖前端、后端、移动端、算法等热门优质文章分享,欢迎大家前去体验!

前言

本系列共有以下几个章节:

您此次阅读的是第三章节:定情信物传声筒port。

注:以上所有文章都被归档到:https://github.com/LinDaiDai/niubility-coding-js 中 ,案例都上传至:https://github.com/LinDaiDai/electron-ipc-example ,欢迎 Star,感谢 Star。

大纲

在之前的文章中,我们主要介绍了 ipcMainipcRenderer 是如何实现主进程与渲染进程、以及渲染进程与渲染进程进行通信的。大家不难发现,之前介绍的方式都非常依赖主进程,特别是渲染进程之间的通信,每次都需要主进程这个中间人来传话,难道就没有什么更简单点的方式吗?

咦,这还真有一个,那就是利用 MessagePort ,通俗易懂的翻译过来:消息端口...(啪,就你英语好是吧)

大家别急,还不知道是啥玩意的话,让我来给大家介绍一下。用个小故事来简单举个例子哈:

渲染进程一(某先生)和渲染进程二(某女士)通过媒人介绍认识,由于男俊女美且三观相符,很快两人就看对眼坠入爱河了,但是奈何工作地点不在一起,不得不异地。这刚认识还处于暧昧期的俩年轻人咋能忍住不联系呢。于是互相交换了手机号加上了wx,相亲结束后,每日通过手机互相联系,增进感情,不日,便确定关系,订婚,结婚,买房,生娃......停停停,给我回来。

咳咳咳,故事呢,其实到 "每日通过手机互相联系" 就结束了哈,后面自行脑补。在上面这则小故事中,媒人就是主进程,渲染进程一和二在最初,会通过主进程进行一个交换 port 的过程,后续都通过 port 来进行通信,不再依赖主进程了。

乍一看,是不是觉得这种通信方式比前面介绍的那些靠谱多了,而且还不需要通过主进程中继的性能开销。

讲完了故事,聊了个大概,让我们来看看本篇文章大纲吧:

electron什么时候出(大概是全网最详细的Electron)(2)

image.png

1.MessagePort的基础用法1.1 如何创建MessagePort

首先还是得先来看看 MessagePort 的基础用法。MessagePort 对象的创建依赖于 MessageChannel 类:

const channel = new MessageChannel(); const port1 = channel.port1 const port2 = channel.port2 // 或者简写为: const { port1, port2 } = new MessageChannel();

实例化 MessageChannel 类之后,就产生了两个 portport1 和 port2 。这两个 port 就是 MessagePort 对象。它就是我们上面那则故事提到的,可以用于两个进程之间进行长期通信的关键所在。

举个小例子,假设现在:

那么现在这两个进程就可以通过 port.onmessageport.postMessage 来收发彼此间的消息了:

// 渲染进程一: port1.onmessage = (event) => { console.log('received result:', event.data) }; port1.postMessage('我是渲染进程一发送的消息'); // 渲染进程二: port2.onmessage = (event) => { console.log('received result:', event.data) }; port2.postMessage('我是渲染进程二发送的消息');

只要 port1port2 一直都存在,它们就可以进行持久通信,怎么样,是不是很 niubility

OKK,那么现在如果是在渲染进程一创建的这两个 port ,关键就是如何把 port2 给到另一个渲染进程二了。也就涉及到了 MessagePort 的传递。

electron什么时候出(大概是全网最详细的Electron)(3)

image.png

1.2 ipcRenderer.postMessage()

说到 MessagePort 的传递就得谈到 ipcRenderer 对象的 postMessage 方法了。因为 MessagePort 对象就是依靠它来传递。没错,此时应该有小伙伴可能想起来了,我们平常网页上的 window 对象也有一个 postMessage 方法,这两者之间其实挺像的,只不过呢,是在不同的通道上。

portMessage 它的参数如下:

ipcRenderer.postMessage(channle, message, [transfer])

前两个好理解,其实和 ipcRenderer 的其它方法差不多,事件,以及传递的消息。第三个参数有些特别,它是一个数组,其中可以传递 MessagePort 对象。这里需要注意,别看第三个参数标记的是 optional ,但其实它也是需要传递的,如果你不需要传 MessagePort 对象,那么就需要定义一个空数组,否则就会报错啦。

另外,在之前的文章中,我们还有用到 ipcRenderer 的其它方法: sendinvokesendSync ,这三种方法主进程都是可以给渲染进程传递返回结果的,比如:

// render.js const 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 的时候,如果不给其他人,自己用来和渲染进程通信也可以呀。

electron什么时候出(大概是全网最详细的Electron)(4)

image.png

2. 主进程与渲染进程通信案例分析

好嘞,扯了这么多,让我们先写个小 demo,来看看通过 MessagePort 主进程与渲染进程是如何通信的吧。

和之前一样,让我们确定下要做什么事:

下面是 demo 的时序图:

electron什么时候出(大概是全网最详细的Electron)(5)

image.png

第一步、调整目录结构

由于这个案例说的是渲染进程与主进程的通信,让我们基于之前的分支 example-3 再新建一个 example-4,同时删除我们不用的窗口2,此时目录结构变为:

(example-4: https://github.com/LinDaiDai/electron-ipc-example/tree/example-4)

electron什么时候出(大概是全网最详细的Electron)(6)

image.png

第二步、渲染进程提供生成 MessagePort 并发送给主进程的能力

之前提到了,做的第一件事:

这里的某个时机,我们就在页面上定义两个按钮吧:

  1. 点第一个按钮创建并发送 port
  2. 点第二个按钮给主进程发送消息

// 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.js const { ipcRenderer } = require('electron') let portToMain function 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.js const { 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.js const ipc = require('./ipc') // ...其他代码

在上面的代码中,我们先用 ipcMain 保证能接收到渲染进程发送过来的 port ,再调用 port1.start() ,然后给它绑定 message 事件,之后渲染进程一发送过来消息都能接收到,也能通过 port1 给渲染进程一发。

第四步、效果演示

一切代码准备就绪,让我们启动项目来看看效果。

分别点击窗口中的第一个和第二个按钮,能够看到主进程和渲染进程的打印日志:

electron什么时候出(大概是全网最详细的Electron)(7)

image.png

(终端里主进程的打印中文会乱码,还请理解…)

electron什么时候出(大概是全网最详细的Electron)(8)

image.png

3. 渲染进程与渲染进程案例预告

在看完了上面的案例之后,相信你对于这种用 postMessage 进行窗口间通信的方式有了一些了解。实际的开发场景中,我们可能还会进行渲染进程与渲染进程间的通信,甚至是同一个进程内部之间的通信。在后面的文章中,我们会介绍如何通过 postMessage 来实现一个比较通用的 electron ipc 通信的库,你也可以先利用上面的知识自己尝试着看看可以如何去写,敬请期待哦。

后语

这篇文章就介绍到这里。通过这三个章节的介绍,相信你对 ipc 大致的通信都有所了解了吧。希望在实际的开发中能够帮助到你。

我会不定时的更新一些前端方面的知识内容以及自己的原创文章

你的鼓励就是我持续创作的主要动力 .

,