大概是全网最详细的Electron ipc 讲解(一) | 您所在的位置:网站首页 › invoke方法返回值 › 大概是全网最详细的Electron ipc 讲解(一) |
希沃ENOW大前端 公司官网:CVTE(广州视源股份) 团队:CVTE旗下未来教育希沃软件平台中心enow团队 本文作者: 前言你盼世界,我盼望你无 bug 。Hello 大家好!我是霖呆呆。 距离上一次我们相遇,似乎间隔的太久了...应该还是去年了... 对于之前文章的搁置,在此说一声抱歉,今年会慢慢补上来(╹▽╹)。(不过还是那句话,那么靓的你肯定会原谅我的啦) OKK,谈谈文章吧。由于近期的工作内容主要是以桌面端开发为主,所以会多出一些 electron 相关的文章。这不,这个系列主要就是讲解 electron 中 ipc 模块的内容。文章质量和内容这一块大家可以放心,呆呆还是会以通俗易懂的方式,并结合实际案例去讲好每一块知识点,再对各个部分加以总结。 但由于是系列文章,所以肯定也会有很多相对基础的内容,大佬请略过,如果是 electron 初学者的话,相信你会有所收获(* ̄︶ ̄)。 本系列共有以下几个章节: 主进程与渲染进程的两情相悦 渲染进程与渲染进程的搭桥牵线 定情信物传声筒port您此次阅读的是第一章节:主进程与渲染进程的两情相悦。 注:以上所有文章都被归档到: github.com/LinDaiDai/n… 中 ,案例都上传至:github.com/LinDaiDai/e… ,欢迎 Start,感谢 Start。 案例版本信息: electron: v13.6.7 Nodejs: v16.13.2 大纲谈到 electron 中进程的通信,就不得不谈到官方提供的 ipc 模块。这种方式的通信主要是依赖 electron 提供的 ipcMain 和 ipcRenderer 两个模块。 关于通信分为以下几部分: 渲染进程发送同步/异步消息给主进程 主进程发送同步/异步消息给渲染进程 1. 渲染进程发送同步/异步消息给主进程渲染进程依赖 ipcRenderer 模块给主进程发送消息,官方提供了三个方法: ipcRenderer.send(channel, ...args) ipcRenderer.invoke(channel, ...args) ipcRenderer.sendSync(channel, ...args)channel 表示的就是事件名(消息名称), args 好理解,也就是参数。 这三种方法都可以给主进程发送消息,同时还可以等待主进程的答应,真所谓是 两情相悦,互相来电 ,只是答应的方式不同。 1.1 ipcRender.send 案例渲染进程 render.js: // render.js const { ipcRenderer } = require('electron'); function sendMessageToMain() { ipcRenderer.send('render-send-to-main', '我是渲染进程通过 send 发送的消息'); }主进程 main.js: // main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-to-main', (event, message) => { console.log(`receive message from render: ${message}`) })1、主进程通过 ipcMain.on 来监听渲染进程的消息; 2、主进程接收到消息后,可以回复消息,也可以不回复。如果回复的话,通过 event.reply 发送另一个事件,渲染进程监听这个事件得到回复结果。如果不回复消息的话,渲染进程将接着执行 ipcRenderer.send 之后的代码。 上面提到过了, send 这样的方式,主进程可以给回复,也可以不给回复,但是得通过 event.replay。如果此时你试图用 return 的方式传递返回值的话,结果并不能达到你的预期。 // render.js const { ipcRenderer } = require('electron'); function sendMessageToMain() { const replyMessage = ipcRenderer.send('render-send-to-main', '我是渲染进程通过 send 发送的消息'); console.log(replayMessage); // undefined } // main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-to-main', (event, message) => { console.log(`receive message from render: ${message}`) return '主进程试图传递消息给渲染进程'; })send 方法的返回值是 undefined ,即使在 ipcMain.on 中试图 return 返回值也是不是行的。 正确的做法是通过 event.reply 发送另一个事件,渲染进程监听这个事件得到回复结果: 渲染进程 render.js: // render.js const { ipcRenderer } = require('electron'); // send 方法发送,并绑定另一个事件接收返回值 function sendMessageToMain() { ipcRenderer.send('render-send-to-main', '我是渲染进程通过 send 发送的消息'); } ipcRenderer.on('main-reply-to-render', (event, message) => { console.log('replyMessage', message); // 'replyMessage 主进程通过 reply 回复给渲染进程的消息' })主进程 main.js: // main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-to-main', (event, message) => { console.log(`receive message from render: ${message}`) event.reply('main-reply-to-render', '主进程通过 reply 回复给渲染进程的消息') }) 1.2 ipcRender.invoke 案例渲染进程 render.js: // render.js const { ipcRenderer } = require('electron'); async function invokeMessageToMain() { const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息'); console.log('replyMessage', replyMessage); }主进程 main.js: // main.js const { ipcMain } = require('electron'); ipcMain.handle('render-invoke-to-main', async (event, message) => { console.log(`receive message from render: ${message}`) const result = await asyncWork(); return result; }) const asyncWork = async () => { return new Promise(resolve => { setTimeout(() => { resolve('延迟 2 秒获取到主进程的返回结果') }, 2000) }) }1、主进程通过 ipcMain.handle 来处理渲染进程发送的消息; 2、主进程接收到消息后,可以回复消息,也可以不回复。如果回复消息的话,可以通过 return 给渲染进程回复消息;如果不回复消息的话,渲染进程将接着执行 ipcRenderer.invoke 之后的代码。 3、渲染进程异步等待主进程的回应, invoke 的返回值是一个 Promise 。 为了验证第三点,我们可以不使用 await 来接收 invoke 的返回结果,看看会是什么样: // render.js const { ipcRenderer } = require('electron'); function invokeMessageToMain() { const replyMessage = ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息'); console.log('replyMessage', replyMessage); // Promise }打印的结果符合我们的预期,是一个 Promise。 这里有小伙伴可能会有疑问了,会不会是因为 ipc.Main 那边是一个异步执行函数才会造成这种情况呢?好的,我们将 main.js 也来改造一下: // render.js const { ipcRenderer } = require('electron'); function invokeMessageToMain() { const replyMessage = ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息'); console.log('replyMessage', replyMessage); // Promise } // main.js ipcMain.handle('render-invoke-to-main', (event, message) => { console.log(`receive message from render: ${message}`) const result = '我是主进程同步的返回结果'; return result; })我们把 ipcMain.handle 的返回值变为了一个普通的字符串,发现 ipcRender.invoke 那边返回的还是一个 Promise。 这也就验证了我们上面的结论。 1.3 ipcRender.sendSync 案例渲染进程 render.js: // render.js const { ipcRenderer } = require('electron'); function sendSyncMessageToMain() { const replyMessage = ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程通过 syncSend 发送给主进程的消息'); console.log('replyMessage', replyMessage); // '主进程回复的消息' }主进程 main.js: // main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-sync-to-main', (event, message) => { console.log(`receive message from render: ${message}`) event.returnValue = '主进程回复的消息'; })1、主进程通过 ipcMain.on 来处理渲染进程发送的消息; 2、主进程通过 event.returnValue 回复渲染进程消息; 3、如果 event.returnValue 不为 undefined 的话,渲染进程会等待 sendSync 的返回值才执行后面的代码; 4、请保证 event.returnValue是有值的,否则会造成非预期的影响。 前面两点都好理解,关于第三点,会有点绕,这边我们来再写两个例子帮助你理解。 上面的案例,主进程绑定的处理函数是一个同步的,我们将它换为异步的来看看: // main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-sync-to-main', async (event, message) => { console.log(`receive message from render: ${message}`) const result = await asyncWork(); event.returnValue = result; }) const asyncWork = async () => { return new Promise(resolve => { setTimeout(() => { resolve('延迟 2 秒获取到主进程的返回结果') }, 2000) }) }这次我们在执行完一个异步函数 asyncWork 之后再给 event.returnValue 赋值。 结果发现渲染进程那边会在 2 秒之后才打印: "replyMessage 延迟 2 秒获取到主进程的返回结果"而且对于渲染进程,以下两种写法,结果都是一样的: // render.js const { ipcRenderer } = require('electron'); function sendSyncMessageToMain() { const replyMessage = ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程通过 syncSend 发送给主进程的消息'); console.log('replyMessage', replyMessage); // 'replyMessage 延迟 2 秒获取到主进程的返回结果'' } // 或者改用 async 函数 async function sendSyncMessageToMain() { const replyMessage = await ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程发送给主进程的同步消息'); console.log('replyMessage', replyMessage); // 'replyMessage 延迟 2 秒获取到主进程的返回结果' }也就是说,不论渲染进程在接收 sendSync 结果的时候,是不是用 await 等待,都会等待结果返回后才向下执行。但如果你已经确定你的请求是一个异步的话,建议还是使用 invoke 去发送消息,这里出于两点原因考虑: 1、方法名 sendSync 就很符合语义,发送同步消息; 2、请求执行的明明是异步代码,但是如果你用 const replyMessage = ipcRenderer.sendSync('xxx') 方式来获取响应信息,会很奇怪。 OKK,上面的第四点谈到了,请保证 event.returnValue 是有值的,否则会照成非预期的影响。让我们也来写个例子: // render.js const { ipcRenderer } = require('electron'); function sendSyncMessageToMain() { const replyMessage = ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程通过 syncSend 发送给主进程的消息'); console.log('replyMessage', replyMessage); // replyMessage {error: "reply was never sent"} console.log('next'); // 这里也会执行 } // main.js ipcMain.on('render-send-sync-to-main', async (event, message) => { console.log(`receive message from render: ${message}`) })在上面的例子中,主进程那边不对 event.returnValue 做处理,在渲染进程这边将会得到一个错误: {error: "reply was never sent"}虽然 next 也会打印,但是如果你再想去发送一次 render-send-sync-to-main 你会发现页面已经卡了... 好的,文章阅读到现在,其实也差不多了。后面的主进程给渲染进程发送消息的方式,其实在介绍渲染进程发送消息的时候,算是已经介绍过了,不过还是让我们来看看吧。 2. 主进程发送同步/异步消息给渲染进程与 ipcRenderer 模块对应的就是 ipcMain 了。主进程依赖 ipcMain 模块给渲染进程发送消息,主要有以下几种方式: event.reply:主进程通过 on 监听消息,如果渲染进程用 send 发送消息时,可以在 on 的回调函数里获取到事件对象并通过 event.reply 发送另一个事件; return :主进程通过 handle 监听处理消息,如果渲染进程用 invoke 发送消息时,可以在 on 的回调函数里通过 return 回复消息; event.returnValue :主进程通过 on 监听消息,如果渲染进程用 sendSync 发送消息时,可以在 on 的回调函数里通过设置 event.returnValue 回复消息; window.webContents.send :主进程通过窗口实例的 webContents 给本窗口内的渲染进程发送消息。前面三种在渲染进程发送消息那一部分已经介绍过了,接下来让我们来看一下 window.webContents 这种方式吧。 2.1 window.webContents.send 案例 :这种方式依赖于 webContents 对象,它是我们在项目中新建窗口时,产生的窗口对象上的一个属性。 例如我们在一个窗口完成加载时,发送消息: // main.js const { app, BrowserWindow } = require('electron') function createWindow() { const window = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, contextIsolation: false }, }) window.loadFile('src/index.html') // 窗口在完成加载时,通过 webContents.send 给渲染进程发送消息 window.webContents.on('did-finish-load', () => { window.webContents.send('main-send-to-render', '启动完成了') }) } app.whenReady().then(createWindow)渲染进程 render.js : // render.js const { ipcRenderer } = require('electron'); ipcRenderer.on('main-send-to-render', (event, message) => { console.log(`receive message from main: ${message}`) })通过以上操作,也可以达到主进程主动给渲染进程发送消息的效果(* ̄︶ ̄)。 3. 总结OKK,介绍完了功能,分析完了案例,让我们来加以总结一下吧。 如果通过 ipcMain 和 ipcRenderer ,渲染进程有三种方式给主进程发送消息,即: ipcRenderer.send(channel, ...args) ipcRenderer.invoke(channel, ...args) ipcRenderer.sendSync(channel, ...args) 通过上面三种方式,主进程都可以有相应的方法给予渲染进程答应,只是答应的方法不同; 同步请求建议用 sendSync,异步请求建议用 invoke; 主进程中还可以获取到某个渲染进程的 window.webContents ,然后通过 window.webContents.send 给这个渲染进程发送消息;温馨提示: 案例都上传至:github.com/LinDaiDai/e… ,欢迎 Start,感谢 Start。 后语这篇文章就介绍到这里。可以发现,这篇文章要讲解的知识点其实还是蛮基础的,但呆呆在刚开始学的时候,也没有发现有特别好的教材讲解,所以这不是"自己动手,丰衣足食"整了一篇吗? 哈哈哈,不过放心,这个系列还是会继续下去的,毕竟 ipc 也还有好些东西要讲,让我们一起期待一下下篇文章:渲染进程与渲染进程的搭桥牵线吧。 喜欢「霖呆呆」的小伙还希望可以关注霖呆呆的公众号 LinDaiDai。 我会不定时的更新一些前端方面的知识内容以及自己的原创文章🎉 你的鼓励就是我持续创作的主要动力 😊. |
CopyRight 2018-2019 实验室设备网 版权所有 |