Skip to content

进程间通信

进程间通信是通过预加载脚本来联系主进程和渲染进程的

创建预加脚本

这里我所有的通信都通过三个方式进行

js
// mainPreload.mjs
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('mainAPI', {
  sendToMain: (params) => ipcRenderer.send('fromRender', params), // 用于渲染进程发送到主进程 params:{event:"xxx",args:XXX}
  fromMain: (callback) => ipcRenderer.on('sendToRender', callback), // 用于主进程发送到渲染进程 win.webContents.send('sendToRender', params)
  invokeMain: (params) => ipcRenderer.invoke('eventRender', params), // 用于渲染进程调用主进程方法 window.mainAPI.invokeMain({event:"xxx",args:XXX})
})

加载预加载脚本

js
// index.js
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: true, // 需开启
      enableRemoteModule: true,
      preload: path.join(__dirname, 'mainPreload.mjs')
    }

注册接收渲染进程的事件,和发送信息到渲染进程

以下为当前electron/index.js完整代码

js
import { app, BrowserWindow, ipcMain } from 'electron'
import os from 'os'
import path, { dirname } from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

let mainWindow
function createWindow () {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: true,
      enableRemoteModule: true,
      preload: path.join(__dirname, 'mainPreload.mjs')
    }
  })
  loadPage()
  // 监听页面加载完成
  mainWindow.webContents.on('did-finish-load', () => {
    // 发送数据到渲染
    mainWindow.webContents.send('sendToRender', { event: 'init', data: 'main init' })
  });
}

function loadPage() {
  if (app.isPackaged) {
    mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
    // mainWindow.loadURL('http://localhost:8100/')
    // mainWindow.webContents.openDevTools()
  } else {
    mainWindow.webContents.openDevTools()
    mainWindow.loadURL('http://localhost:8100/')
  }
}

/**
 * 当应用准备好时
 */
app.whenReady().then(() => {
  createWindow()
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
})

ipcMain.on('fromRender', (_, params) => {
  console.log('fromRender', params)
  switch (params.event) {
    case 'print':
      mainWindow.webContents.print()
      break
  }
})

ipcMain.handle('eventRender', (_, params) => {
  switch (params.event) {
    case 'hostInfo':
      return {
        hostname: os.hostname(),
        arch: os.arch(),
        platform: os.platform(),
        release: os.release(),
        version: os.version()
      }
  }
})

在渲染进程中接收或发送

tsx
import React, { useEffect, useState } from 'react';
import { Button } from 'antd';

const isElectron = ()=>{
  // 判断是否运行在electron环境中
  return window && window.mainAPI && window.mainAPI.invokeMain
}

const Home: React.FC = () => {
  const [text, setText] = useState('')
  useEffect(() => {
    // 组件加载完成,监听主进程发送过来的数据
    if (isElectron()){
      window.mainAPI.fromMain((_: any, data: any) => {
       switch (data.event) {
          case 'init':
            console.log('收到主进程发送的数据', data.data);
          break;
       }
      })
    }
  }, [])

  const handleSend = () => {
    if (!isElectron()) return
    // 发送数据到主进程
    window.mainAPI.sendToMain({ 'event': 'update', args: { newVersion:' 1.0.2' }})
  }

  const handleGetHostInfo = () => {
    if (!isElectron()) return
    window.mainAPI.invokeMain({ 'event': 'hostInfo' }).then((res: any) => {
      console.log('收到主进程发送的数据', res);
      setText("系统信息"+JSON.stringify(res))
    })
  }

  return(
    <div>
      <div>
        <Button style={{ marginRight: 10 }} type="primary" onClick={handleSend}>发送数据到主进程</Button>
        <Button style={{ marginRight: 10 }} onClick={handleGetHostInfo}>获取系统信息</Button>
      </div>
      <div>{text}</div>
    </div>
  )
};

export default Home;

注意:我在这里渲染进程是ts环境的,需要给window添加类型定义,

ts
// electron.d.ts
declare global {
  interface Window {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    mainAPI: {
      sendToMain: (params: any) => void;
      fromMain: (callback: (event:any, params: any) => void) => void;
      invokeMain: (params: any) => Promise<any>;
    };
  }
}

export {};

注意:electron使用ESM规范

  1. 我这里是基于ESM规范写的,electron官方要electron28才支持
  2. 预加载脚本需为.mjs文件
  3. 需解决 __dirname 不存在的问题,解决方法主要为以下几行代码
js
import path, { dirname } from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

Released under the MIT License.