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()需要在渲染进程页面初始化时先调用一次

实用场景

Logo

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

更多推荐