前言
Electron进程通信主要有主 -> 渲染进程, 渲染 -> 主进程, 渲染进程 -> 渲染进程,本文主要从可用性,便捷性,通用型来处理通信;
你将收获如下:
- ipcMain和ipcRenderer的基本使用
- 通信类的封装
先说一下场景
一个项目有很多跟桌面交互的场景,比如打开文件、选择文件(文件夹);
electron提供给我们的ipc是ipcRenderer和ipcMain,拿选择文件夹举例,我的流程应该是
- ipcMain监听
channel名称为selectFolder的请求 - ipcRenderer发送一个
channel为selectFolder给主进程 - 主进程监听到了这个事件后,进行
dialog.showOpenDialogSync,然后返回给渲染进程
以上流程可以实现功能,但是有一些小问题
- 比如多个页面都要选择文件夹,要写多套
ipcRenderer.send('selectFolder'),重复不好维护,万一哪天ipcMain改了监听的channel名字,所有渲染进程的selectFolder都得跟着改 好像没其他的了,写不下去了
根据上面的流程,我们分析了很多类似的操作都可以放到一个类里面去维护,比如选择文件、选择文件夹、打开文件这类属于system类型的操作,可以放到一个systemIPC类;请求接口等可以放到一个networkIPC类中;
编写我们的IPC类
由于存在主进程和渲染器进程,所以对应都要写一个基础类,然后在基础类上进行定义业务所需要的方法;
主进程的IPC类,主要实现了一个IpcMainBaseController基础类,以及SystemController
class IpcMainBaseController {
prefix: string
constructor(prefix: string) {
this.prefix = prefix
}
getChannelName(name: string) {
return [this.prefix, name].join(':')
}
}
import type { IpcMainEvent } from 'electron'
import { BrowserWindow, screen, desktopCapturer, clipboard, nativeImage } from 'electron'
export class SystemController extends IpcMainBaseController {
constructor() {
super('system')
}
async closeWindow(event: IpcMainEvent) {
return event.sender.close()
}
async hideWindow(event: IpcMainEvent) {
const window = BrowserWindow.fromWebContents(event.sender)
window?.hide()
}
async showWindow(event: IpcMainEvent) {
const window = BrowserWindow.fromWebContents(event.sender)
window?.show()
}
async getScreenSize() {
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.workAreaSize
return {
width,
height
}
}
}
渲染进程的IPC类,同样编写一个基础类IpcRendererService还有业务类SystemIpcRendererService
import type { IpcRenderer } from 'electron'
export class IpcRendererService {
prefix: string
ipcRenderer: IpcRenderer
constructor(prefix: string) {
this.prefix = prefix
this.ipcRenderer = (window.electron.ipcRenderer || window.ipcRenderer) as unknown as IpcRenderer
}
async invoke(channel: string, params?: Object): Promise<any> {
try {
const fullChannel = `${this.prefix}:${channel}`
this.initCallbackChannel(channel, options)
const result = await this.ipcRenderer.invoke(
fullChannel,
params || {},
)
return result
} catch (error) {
console.error('[invoke error]: ', error)
throw error
}
}
}
export class SystemIpcRendererService extends IpcRendererService {
constructor() {
super('system')
}
async closeWindow() {
return await this.invoke('closeWindow')
}
async showWindow() {
return await this.invoke('showWindow')
}
async hideWindow() {
return await this.invoke('hideWindow')
}
async getScreenSize() {
return this.invoke('getScreenSize')
}
}
export const systemIpcRendererService = new SystemIpcRendererService()
主进程进行channel监听
import { ipcMain } from 'electron'
import type { BrowserWindow } from 'electron'
import { IpcMainBaseController } from './base'
import { SystemController } from './system'
export const enumControllerMethods = <T extends IpcMainBaseController>(clsInstance: T) => {
const result = {}
const filterKeys = ['constructor']
const keys = Object.getOwnPropertyNames(clsInstance.constructor.prototype)
keys.forEach((key) => {
if (filterKeys.includes(key)) {
return
}
const serviceFunction = clsInstance[key]
if (typeof serviceFunction === 'function') {
const channel = clsInstance.getChannelName(key)
// 这段是关键,通过getChannelName拿到需要监听的channel,进行事件绑定
ipcMain.handle(channel, serviceFunction.bind(clsInstance))
result[channel] = serviceFunction
}
})
return result
}
export const registerMainHanlders = (mainWindow: BrowserWindow) => {
const system = new SystemController()
enumControllerMethods(system)
}
使用(vue3)
<script setup lang="ts">
import { reactive } from 'vue'
import { systemIpcRendererService } from '@/ipc/system'
const hideWindow = async (item) => {
await systemIpcRendererService.hideWindow()
}
const showWindow = async (item) => {
await systemIpcRendererService.showWindow()
}
</script>
<template>
<div class="py-6">
<div @click="hideWindow">隐藏window</div>
<div @click="showWindow">显示window</div>
</div>
</template>
<style lang="less" scoped></style>
最后
本文只是提供了一个简单的思路,方便项目进行channel的维护;github代码,完整示例请看https://github.com/rictt/electron-practices