AI 摘要

Electron+React使用contextBridge进行IPC通信 本文介绍了如何在Electron和React中使用contextBridge进行IPC通信。Electron是一个开源框架,可用于开发跨平台的桌面GUI应用程序,而React是一个前端框架。在过去,我们通常使用ipcMain和ipcRenderer进行异步通信,但从Electron 12开始,默认启用了上下文隔离模式,并建议使用contextBridge进行通信。 contextBridge是一个模块,用于将API暴露给渲染进程。它应该被写入Electron的preload.ts(或preload.js)文件中。使用contextBridge的好处是不需要在渲染进程中处理Node.js模块,并且可以像调用函数一样使用。这对于React开发非常适用,尤其是在使用Hooks时。 在Electron中,主进程使用Node.js与操作系统进行交互,因此需要在主进程中引入ipcMain模块。通过定义相关的接口,我们可以在React中调用这些API。 在渲染进程的文件中,当我们第一次使用window对象时,可能会遇到TypeScript的警告。为了解决这个问题,可以在types文件夹中的global.d.ts文件中添加相应的接口声明。 现在,我们可以在React组件中调用API并进行相应的通信。通过点击按钮,我们可以将消息从主进程传递给渲染进程,并更新页面上的内容。 使用contextBridge可以有效地进行Electron和React之间的IPC通信,并保持代码的整洁和可读性。

封面画师:水卯

Twitter:@Minau_

Electron是GitHub开发的一个开源框架。通过使用Node.js和Chromium的渲染引擎来完成跨平台的桌面GUI应用程序的开发。React是Facebook开发的前端框架。本文主要是为了排Electron的contextBridge上下文隔离的雷。

上下文隔离

以往采用的是ipcMain与ipcRenderer直接进行异步通信,但从Electron 12以来,默认启用了上下文隔离模式,并作为建议安全设置。

关于上下文隔离开启前后的区别,读者可以前往 上下文隔离|Electron 进行了解,文章不再赘述。

contextBridge

contextBridge是用于从预加载脚本将API暴露给渲染进程(Renderer)的模块,也就是说,contextBridge应该被写在你Electron开发的preload.ts(preload.js)文件里。

/* preload.ts */
const { ipcRenderer, contextBridge } = require('electron');

contextBridge.exposeInMainWorld('API', {
  send: (channel: string, message: any) => {
    if(channel === 'toMain'){
      console.log('received');
      ipcRenderer.send(channel, message);
    }
  },
  receive: (channel: string, func: any) => {
    if (channel === 'fromMain'){
      console.log('readytosend');
      ipcRenderer.on(channel, (event, args) => func(args));
    }
  },
});

采用contextBridge的优势是不需要在渲染进程中处理Node.js模块,并能像函数一样进行调用,非常适合React开发,尤其是Hooks使用。

在Electron中,主进程是用Node.js来与OS进行交互的,因此需要在主进程中引入ipcMain模块。

/* main.ts */
import { ipcMain } from 'electron';

/* Your code */

ipcMain.on('toMain', (event, arg: string) => {
  console.log(arg);
  event.reply('fromMain', 'This is a message from main');
});

此时Electron相关的接口已经设计好,开始在React中进行调用。

/* App.tsx */
import React, { useState } from 'react';

export default function App() {
    const [testValue, setTestValue] = useState("Ready");
    window.API.receive("fromMain", (arg: string) => { setTestValue(arg) });
    return (
        { /* Your code */ }
        <button
            type="primary"
            onClick={() => { window.API.send('toMain', 'testSend') }}
        >
            Test
        </button>
        <label>
            Input:
            <input type="text" value={testValue}/>
        </lable>
    )
}

此时点击页面上的Test按钮,即可将input控件中的文字从Ready改成This is a message from main

关于TypeScript的问题

在渲染进程的文件中第一次输入window的时候,TypeScript会提示 Property 'API' does not exist on type 'Window & typeof globalThis'.ts(2339),有的解决方案会将window改成(window as any),但这种方案其实是不好的,可以对types这一文件夹下的global.d.ts文件添加如下内容以进行interface全局暴露

/* global.d.ts */

export interface IApi {
  send: (arg0: string, arg1: any) => Promise<void>;
  receive: (arg0: string, arg1: any) => Promise<void>;
}

declare global {
  interface Window {
    API: IApi;
  }
}

此时即可保证在 TypeScript 编译器在渲染器进程中编写脚本时清楚API全局window对象的属性