electron 使用ipcRenderer,ipcMain 实现进程之间的通信
通过ipcApi 这个类 就代替了 preload.js 作为中转绑定的效果。
·
官方使用的通信方式
- 官方文档的通信 也是使用ipcMain,ipcRenderer两个模块来做的通信,这两个模块是核心。
- 但是要实现真正的通信需要配合预加载脚本来实现。
- 第一步:创建一个窗口时,必须要写入以下配置,预加载脚本。
// main.js
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
- 第二步:contextBridge.exposeInMainWorld 方法意思为,暴露一个 ‘electronAPI’ ,的对象在window上,可以使用window.electronAPI 来获取这个对象
// preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
// ipcRenderer.send向主进程 发送消息
setTitle: (title) => ipcRenderer.send('set-title', title)
})
- 第三步:调用 第二步暴露出来的对象和方法
// index.html
div.addEventListener('click', () => {
window.electronAPI.setTitle('我是ajin')
})
- 这是官方提供的通信方式,以上通信是渲染进程向主进程通信。想要了解更多官方提供的方式请移步到官方文档 进程之间的通信
preload.js 的代替之法
使用preload.js 的 缺点:
- 维护的心智负担比较大,
- 对于没有接触的人来说 不易于理解
- 写着不舒服,没有遵循内聚准则
解决办法:
- preload.js 相当于中间绑定方法,通过electron 提供的方法把事件绑定到window上。
- 我们可以实现一个类,把proload 做的事情,交给自定义类来做,比如保存 交互方法 、 ipc 对象,app 对象等。
- 后期统一维护这个静态类,所有交互逻辑,事件传参,都通过这个类中转
下面我们通过一个例子来实现,效果如下
- 主进程 上点击关闭应用程序的按钮
- 渲染进程弹出一个自定义modal,询问是否要关闭应用
- 渲染进程点击确定,调用主进程的关闭程序方法
一、主进程 向 渲染进程通信
思路:
- 主进程 向 渲染进程 通信需要 调用 window.webContents.send(key, value) 方法。
- 渲染进程 通过 ipcRenderer.on(channel, listener) 监听 主进程 的调用
- main.js 主进程初始化的时候,把app, BrowserWindow 绑定到类上去
// ipc-api/ipcApi.ts
class IpcApi {
/** BrowserView 创建出来 */
static window : Electron.BrowserView
static app : Electron.App
/** 主进程初始方法 */
static installMain (ipcMain, win, app) {
IpcApi.window = win
IpcApi.app = app
}
}
export default IpcApi
- main.js 调用 IpcApi.installMain,传入参数
import { IpcApi } from '../ipc-api'
import { app, BrowserWindow from 'electron'
const win = new BrowserWindow({ ... })
// ... 省略的其他代码
IpcApi.installMain(win, app)
// ....省略的其他代码
- 定义主进程向 渲染进程发送消息的 方法emit
class IpcApi {
/** BrowserView 创建出来 */
static window : Electron.BrowserView
static app : Electron.App
/** 主进程初始方法 */
static installMain (ipcMain, win, app) {
IpcApi.window = win
IpcApi.app = app
}
/** 向主进程发送消息 */
static emit(key: string, value: any) {
IpcApi.window.webContents.send(key, value)
}
}
export default IpcApi
- 在main.js 中 调用 emit 方法,发送消息
const win = new BrowserWindow({ ... })
win.on('close', (e) => {
e.preventDefault()
IpcApi.emit('onClose', '点击了关闭按钮')
})
- 渲染进程 监听主进程里面的事件调用。得到事件响应
- 使用import 引入 ipcRenderer 通信对象
- 调用 ipcRenderer.on(channel, listener) 监听 channel, 当新消息到达,将通过 listener(event, args…) 调用 listener。
- 使用react 开发,可以把下面代码封装成一个hooks 进行调用
// App.tsx
(async () => {
if (PLATFORM === 'electron') {
const { ipcRenderer } = await import('electron')
ipcRenderer.on('onClose' , (_, e) => {
console.log(e,'---->')
})
}
})()
主进程 向 渲染进程的 单向通信就完成了,重点就是import 的引入 const { ipcRenderer } = await import(‘electron’)
渲染进程 向 主进程通信
思路:
- 注册渲染进程需要调用 主进程 的事件
- 把注册好的事件暴露到全局(window),方便调用
- window.ipcApi.closeWindow() 调用 方法
- 绑定 我们要通信 的 事件,新增 handlersEvent 方法,此方法一定要在installMain 之前。把我们定义好的交互方法放入类里面,后续好进行注册等操作。
type Events = {name: string, fn: Function}[]
class IpcApi {
static events: Events = []
// ... 省略的其他代码
/** 绑定所有的调用 方法,该方法调用,需要在 installMain 之前 */
static handlersEvent(events: Events) {
IpcApi.events = [...events]
}
}
export default IpcApi
- 调用 IpcApi.handlersEvent,定义所有的交互方法。注释的就是关闭窗口方法
// ipc-api/index.ts
import IpcApi from './ipcApi'
IpcApi.handlersEvent([
{
name: 'closeWindow', fn: () => {
console.log('---点击了modal 框的确认,调用下面的方法,关闭应用')
// IpcApi.window = null
// IpcApi.app.exit()
}
},
])
export { IpcApi }
- 到这一步,我们需要修改一下installMain 方法,使用 ipcMain.on 订阅所有的事件。ipcMain.on文档
static installMain (ipcMain: Electron.IpcMain, win: Electron.BrowserView , app: Electron.App) {
/** 把所有的事件,绑定到 ipcMain 上 */
IpcApi.events.forEach(({name, fn}) => {
ipcMain.on(name, () => fn())
})
}
- installRenderer 绑定 send 事件 icpRenderer.send ,调用send,向主进程发送消息。 icpRenderer.send,并把eventsMap 对象返回
static installRenderer(icpRenderer: Electron.IpcRenderer) {
const eventsMap = {}
for (const {name} of IpcApi.events) {
eventsMap[name] = (value) => {
icpRenderer.send(name)
}
}
return eventsMap
}
- 调用 installRenderer,把返回的 eventsMap 挂载到window
// App.tsx
(async () => {
// 是否是electron 环境
if (PLATFORM === 'electron') {
const { ipcRenderer } = await import('electron')
const { IpcApi } = await import('../ipc-api')
// 把方法挂在window上,你也可以挂载在其他的地方
window.ipcApi = IpcApi.installRenderer(ipcRenderer)
}
})()
- App.jsx 完整代码
const App = () => {
// installRenderer,
useEffect(() => {
(async () => {
if (PLATFORM === 'electron') {
const { ipcRenderer } = await import('electron')
const { IpcApi } = await import('../ipc-api')
window.ipcApi = IpcApi.installRenderer(ipcRenderer)
}
})()
}, [])
// 监听,主进程调用的方法
useEffect(() => {
(async () => {
if (PLATFORM === 'electron') {
const { ipcRenderer } = await import('electron')
ipcRenderer.on('onClose', (_, e) => {
console.log('1、点击了关闭按钮,弹出了modal 弹窗')
setExit(true)
})
}
})()
}, [])
const sureClose = async () => {
console.log('2、点击了modal上的确定按钮,即将调用关闭应用方法')
window.ipcApi.closeWindow()
}
return (
<ConfigProvider locale={zhCN}>
<Provider store={store}>
<Modal
open={exit}
title={<><ExclamationCircleOutlined
style={{color: 'rgb(255, 38, 37)', marginRight: '10px'}} />提示</>}
onCancel={() => setExit(false)}
onOk={sureClose}
>
请确认是否关闭?
</Modal>
</Provider>
</ConfigProvider>
)
}
export default App
通过ipcApi 这个类 就 代替了 preload.js 作为中转绑定的效果。
更多推荐
已为社区贡献1条内容
所有评论(0)