Wenzi

渲染进程和主进程之间如何通信

蚊子前端博客
发布于 2026/07/07 00:21
在 electron 中,渲染进程和主进程之间如何通信呢?

在 electron 中,渲染进程和主进程是两个独立的进程,渲染进程,通常就是我们理解的前端渲染,主进程就是 nodejs 进程。两者之间无法直接互相访问,若需要传输数据,则可以有以下的几种方式。

在 electron 应用中,有很多渲染进程和主进程需要交互的场景,这两者之间该如何通信呢?

1. 渲染进程到主进程的单向通信 #

比如一些窗口事件,最小化、关闭、置顶、切换主题,提交日志等之类的事件。渲染进程告知主进程一件事,而无需关心事件结果的,可以使用 ipcRenderer.send + ipcMain.on

在渲染进程中,可以使用 ipcRenderer.send 向主进程发送事件。ipcRenderer 也是从 electron 中导出的,因此该方法需要写在 preload.js 中,然后再通过 contextBridge.exposeInMainWorld() 注入到前端页面中:

// preload.js
import { contextBridge, ipcRenderer } from "electron";

const changeTheme = (theme) => {
    ipcRenderer.send("changeTheme", theme);
}

contextBridge.exposeInMainWorld("electronAPI", {
    changeTheme
});

主进程中使用 ipcMain.on() 接受事件:

// 主进程
import { ipcMain } from "electron";

ipcMain.on("changeTheme", (event, theme) => {
    // 切换主题
});

这种从渲染进程进程向主进程发送消息的方式,是非阻塞式的,渲染进程会直接继续执行后续的操作。

2. 渲染进程到主进程的单向通信但有返回值 #

上面的 ipcRenderer.send + ipcMain.on 组合是异步非阻塞式的,渲染进程不关心也拿不到主进程的返回结果。那还有一种方式,是可以拿到返回结果的:ipcRenderer.sendSync

但这里非常不建议使用这种方式,因为它是阻塞式的,会直接让渲染进程卡住,等待主进程的返回结果

渲染进程:

// preload.js

import { contextBridge,  ipcRenderer } from "electron";

const getDataPath = () => {
    ipcRenderer.sendSync("getDataPath");
}

contextBridge.exposeInMainWorld("electronAPI", {
    getDataPath
});

在主进程中,返回值可以放到 ipcMain.onevent.returnValue 中。注意,这是同步进程,回调函数写成 Promise 是没有作用的:

// 主进程
import { ipcMain } from "electron";

ipcMain.on("getDataPath", (event) => {
    event.returnValue = global.pathConfig.publicPath;
});

// ❌ Promise 是无效的
ipcMain.on("getDataPath", async (event) => {
    // ❌ 无效
    const data = await fs.readFile('./xxx.txt')
    e.returnValue = data // 无效!同步回调里不能写异步
});

在 html 中再调用 getDataPath() 就可以拿到主进程的返回数据了:

// 前端 index.js

// 同步进程,可以直接拿到返回值
const result = window.electronAPI.getDataPath();
console.log("result", result);

虽然这种方式,可以让渲染进程以同步的方式拿到主进程中返回的数据。但我们极度不推荐这么做。因为这种方式

  1. 会阻塞渲染进程主线程,大量使用会造成页面卡死、交互卡顿;
  2. 无法处理异步逻辑,只能同步执行;
  3. 异常无法优雅捕获,容易直接崩溃渲染进程;

若我们就是需要在渲染进程发起请求,然后拿到主进程的数据,怎么办呢?

3. 渲染进程到主进程的双向异步通信 #

比如读取本地文件、写入文件、获取目录列表、获取本机信息、用户目录、打开文件选择框、保存对话框、网络请求等操作,是需要在渲染进程发起请求,然后在主进程处理并返回结果的,可以使用: ipcRenderer.invoke + ipcMain.handle

渲染进程中,通过 preload.js 文件向页面中暴露到页面中。ipcRenderer.invoke()永远返回 Promise 对象,必须用 .then() / await 来接收数据:

// preload.js

import { contextBridge, ipcRenderer } from "electron";

// 在渲染进程中向主进程发送异步消息
const ipcInvoke = (channel, ...arg) => {
  return ipcRenderer.invoke(channel, ...arg);
};

contextBridge.exposeInMainWorld("electronAPI", {
    ipcInvoke
});

html 页面中:

const getOsInfoClick = async () => {
    const info = await window.electronAPI.ipcInvoke("getOsInfo");

    console.log("info", info);
}

在主进程中,需要使用 ipcMain.handle 监听对应的事件:

// 主进程
import { ipcMain } from "electron";
import os from "node:os";

// 无论这里是直接返回还是以 Promise 返回,在渲染进程都是以 Promise 收到数据
ipcMain.on("getOsInfo", () => {
    return {
      platform: os.platform(),
      arch: os.arch(),
      release: os.release(),
      hostname: os.hostname(),
      userInfo: os.userInfo(),
    }
});

在需要渲染进程从主进程获取数据时,推荐使用这种方式。

4. 主进程主动向渲染进程推送数据 #

还有一些场景,是主进程向渲染进程推送数据的,比如定时器、下载进度、多窗口之间通信、系统级原生能力回调(托盘点击、右键菜单等)、全局快捷键、系统剪贴板变更、硬件设备事件、电脑休眠、唤醒、锁屏、解锁、屏幕亮度变化等等。

主进程通过 win.webContents.send('xxx', data) 发送数据,渲染进程用 ipcRenderer.on 接收。若需要把数据通知到多个窗口中,可以先拿到这几个窗口的实例,然后每个实例均调用 win实例.webContents.send()

4.1 渲染进程中监听事件 #

多个窗口之间是共享 preload.js 的,我们在渲染进程中定义好接收方式,所有窗口里的 html 都可以直接使用:

// preload.js
import { contextBridge, ipcRenderer } from "electron";

// subscribe() 用来订阅主进程发送过来的事件消息,返回值是取消方法
const subscribe = (channel: string, callback: (...args: any[]) => void) => {
  const subscription = (_, ...args) => callback(...args);
  ipcRenderer.on(channel, subscription);
  return () => ipcRenderer.removeListener(channel, subscription);
};

contextBridge.exposeInMainWorld("electronAPI", {
    subscribe
});

在 html 文件中:

const App = () => {
    useEffect(() => {
        const listener = (data) => {
            console.log("主进程推送过来的数据", data);
        };

        // 监听主进程的事件,并返回取消方法,用来取消监听
        const cancel = window.electronAPI.subscribe("main-send-renderer", listener);
        
        return () => {
            cancel();
        }
    }, [])
}

4.2 单个窗口发送数据 #

若应用中只有一个窗口时,或者只向主窗口的页面中发送数据时,可以用主窗口的实例来操作。

// 主进程 main.js

const { BrowserWindow, ipcMain } = require('electron')
const mainWindow = new BrowserWindow()

// 任意时机主动发消息(定时器、托盘、快捷键、其他回调都可以)
setInterval(() => {
  mainWindow.webContents.send('main-send-renderer', {
    time: new Date().toLocaleString(),
    msg: '主进程主动推送'
  })
}, 3000)

4.3 向多个窗口发送数据 #

比如有些数据是多个窗口之间共享的,页面显示的状态是要保持一致的,用户在其中一个窗口中操作后,就需要把最新的数据同步到其他窗口中。

// 主进程 main.js

const { BrowserWindow } = require('electron')

function broadcastToAllWindows(channel, data) {
  const allWindows = BrowserWindow.getAllWindows()
  for (const win of allWindows) {
    win.webContents.send(channel, data)
  }
}

// 使用
broadcastToAllWindows('global-notice', { text: '全局通知' })

5. 总结 #

今天我们主要了解了在 elecron 中,渲染进程和主进程之间的几种不同的通信方式。

标签:electron
阅读(11)
Simple Empty
No data