Electron 中MessageChannelMain 通信疑点问题处理
Electron 通信有那些
在electron 应用开发中,已经会处理到朱渲染进程通信问题,我们常用的通信API主进程 IPCMain 渲染进程使用 IPCRenderer。为了符合项目规范和安全性,该篇文章将使用 预加载脚本方式进行通信实例操作。
Electron 通信概览
| Renderer ==> Main | ipcRenderer.sed() | ipcMain.on() |
| Renderer ==> Main | ipcRenderer.invoke() | ipcMain.handle() |
| Main ==> Renderer | event/win.webContents.send() | ipcRenderer.on() |
| Main <==>Renderer | MessageChannelMain | 同主进程一致api |
通信详解
Renderer 到 Main
ipcRenderer.send() + ipcMain.on() [单向,无返回值]
作用:渲染进程向主进程发消息
语法:
预加载脚本中preload.js
// 在preload.js中
const {contextBridge,ipcRenderer} = require('electron');
// 暴露给渲染进程
contextBridge.exposeInMainWorld('API', {
// 提供调用方法 [channel 管道名]
send: (channel,data) => {
ipcRenderer.send(channel,data)
}
})
渲染进程中调用
//渲染进程中调用
window.API.send('RendererToMain','渲染进程发数据到主进程')
在主进程中main.js
// 在main.js 中
const {app, BrowserWindow, Menu, globalShortcut, ipcMain,dialog, MessageChannelMain, webContents} = require('electron');
let win;
//监听渲染进程发送的消息
ipcMain.on('RendererToMain',(event,data) => {
//收到数据后的处理逻辑
})
function createWindow () {
win = new BrowserWindow({
width: 850,
height: 600,
webPreferences: {
sandbox: true,
nodeIntegration: false, //启用Node.js集成,使渲染进程能够使用Node.js的API
contextIsolation: true, //启用上下文隔离,确保渲染进程与主进程之间的安全通信
preload: path.join(__dirname, 'preload.js') // ——dirname 当前文件的目录路径
}
});
win.loadFile('index.html'); //加载index.html文件
}
app.on('ready', () => {
createWindow()
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
/**
*当窗口激活时,判断窗口实例是否存在
*/
app.on('activate', () => {
if (win === null) {
createWindow();
}
});
渲染进程与主进程双向通信
ipcRenderer.invoke() + ipcMain.handle [双向通信有返回值 Promise]
作用:渲染进程请求主进程
语法:
在preload.js中[渲染进程调用时可使用await 等待主进程返回数据 ]
// 前面的导入就不要再重复赘述
const {contextBridge,ipcRenderer} = require('electron');
contextBridge.exposeInMainWorld('API',{
invoke:(channel,data) => {
ipcRenderer.invoke(channel,data)
}
})
在main.js中监听
ipcMain.handle(channel,(event,data) => {
//event :事件对象。
//要return 返回的数据
return data
})
ipcRenderer.sendSync() + ipcMain.on()
同步发送消息会阻塞渲染进程,使用语法上和ipcRenderer.send() 一致,但是需注意在主进程监听的时候,需要用到且必须设置 event.returnValue = 数据 【因为会阻塞渲染进程,故不过多介绍】
主进程向渲染进程发信息
webContents.send() + ipcRenderer.on
作用:主进程主动向渲染进程发信息
语法:
主进程中
//globalShortcut 全局快捷键 用于注册和注销全局快捷键
//globalShortcut.register(accelerator, callback) 注册全局快捷键 【accelerator: 快捷键字符串,如 'CommandOrControl+Shift+F',callback: 回调函数】
// 主进程主动发送
globalShortcut.register('Alt+s', () => {
// 这里没有 event,因为没有渲染进程发消息
// 主进程主动推送给渲染进程
win.webContents.send('contentData');
});
关于事件对象 Event 属性
根据代码来看我们会发现,只用监听消息方的回调函数中会存在事件对象,那么这个事件对象中有那些方法,什么类型,用来干嘛呢
| 方法 | 类型 | 说明 |
| event.sender | webContents | 发信息的渲染进程的webContents |
| event.senderFrame | webFrameMain | 发消息的frame |
| event.returnValue | any | 用于ipcRenderer.sendSync()的返回值 |
| event.reply(事件名,数据) | function | 用于回复消息发送者 |
通信的选择
electron中有很多通信那我们在实际开发中如何选择呢?
接下来就分4个场景来分别选择合适的通信方式
1.渲染进程请求主进程数据 ===》 使用ipcRenderer.invoke() / ipcMain.handle()
2,渲染进程通知主进程 ===》ipcRenderer.send() / ipcMain.on()
3,主进程主动推送消息到渲染进程 ===》 webContents.send() / ipcRenderer.on()
4,同步获取数据 ===》 不推荐sendSync [阻塞渲染进程] 还是推荐使用ipcRenderer.invoke() / handle
注意事项
1.上面的ipc通信方式都只能发送可序列化数据,不能发送 函数, DOM,类实例
2,内存泄漏记得使用removeListener 移出不需要的监听器
上述都是一些常见的IPC通信,还有些修饰符 如,iocMain.once()等相关属性可阅读官方文档
https://electron.nodejs.cn/docs/latest/
通信进阶 MessageChannelMain
首先MessageChannelMain 创建的是一个双向通信管道,两个端点,点对点通信
//创建端点语法
const {post1 , post2} = new MessageChannelMain()
//post1用于主进程,将post2发送给渲染进程
//mainjs完整实例
const {app, BrowserWindow , MessageChannelMain} = require('electron')
// 声明一个变量用于保存主进程端口
let MainPost = null
// 创建窗口函数
function creatWindow(){
const win = new BrowserWindow(
{
width:800,
height:600,
webPreferences:{
preload:path.join(_dirname, 'preload.js')
ContextIsoLation:true
}
}
)
win.loadFile('index.html')
// 逻辑
监听页面加载事件触发后将post2端口暴露给渲染进程
win.wenContents.on('did-finish-load', () => {
// 页面加载完成,创建端口
const {post1,post2} = new MessageChannelMain()
//将post1 赋值给 MainPost 方便后续外部调用
MainPost = post1
// 把post2暴露出去,注意看我这里用到是什么方法,为什么不是使用的send
win.webContents.portMessage('事件名',null , [post2])
// 监听渲染进程发来的消息
MainPost.on('message',(e)=> {
// e收到的数据,在根据数据进行不同的处理eg:
switch(e.data.type){
case:'1',
// post1 向 post2 发信息
MainPost.postMessage({任意数据})
backe
}
})
//非常重要!!!要想监听到数据必须调用
MainPost.start()
})
}
发送post2的时机也是具有一定要求的【我提供一张图篇进行说明】

现在看是不是只需要要预加载脚本中去监听 发送post2 这个事件拿到post2暴露给渲染进程就可以了。那么你将会得到undefind
当 contextIsolation: true 时,通过 contextBridge 传递对象会被克隆 但

那此时通过contentBridge暴露出去的post2中的方法是不能行的,那解决办法呢,在preload 中对post2存在的方法但是实现一次在、暴露给渲染进程
// preload.js - 支持多个监听器版本
let messagePort = null;
let messageHandlers = []; // ← 改成数组
contextBridge.exposeInMainWorld('messageChannel', {
init: (onReady) => {
ipcRenderer.on('port', (event) => {
// event.ports[0] 就是主进程传递出来的post2
messagePort = event.ports[0];
messagePort.onmessage = (e) => {
// 调用所有注册的处理函数
messageHandlers.forEach(handler => handler(e.data));
};
onReady?.();
});
},
// 添加监听器(返回移除函数)
onMessage: (handler) => {
messageHandlers.push(handler);
// 返回一个函数用于移除这个特定的监听器
return () => {
const index = messageHandlers.indexOf(handler);
if (index > -1) {
messageHandlers.splice(index, 1);
}
};
},
// 移除所有监听器
offAllMessages: () => {
messageHandlers = [];
},
send: (data) => {
messagePort?.postMessage(data);
},
close: () => {
messagePort?.close();
messagePort = null;
messageHandlers = [];
}
});
init()需要在渲染进程页面初始化时先调用一次

实用场景

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)