用示例讲解下渲染进程到主进程的双向通信
初始版本项目结构可参考项目:https://github.com/ylpxzx/electron-forge-project/tree/init_project
渲染进程到主进程(双向)
以Electron官方文档给出的”渲染进程触发动作,等待主进程返回内容“为例。
Electron的双向通信采用ipcMain.handle(electron主进程内部监听)、ipcRenderer.invoke(包装给前端页面)
实现整项目示例:https://github.com/ylpxzx/electron-forge-project/tree/render_to_main_two_way
ipcMain.handle是 Electron 中用于处理异步 IPC(进程间通信)调用的方法。ipcMain.handle(‘dialog:openFile’, handleFileOpen) 的作用是注册一个处理函数 handleFileOpen,当渲染进程(通常是你的 Vue 应用)发送一个名为 dialog:openFile 的异步消息时,这个处理函数会被调用并返回处理结果。
通信逻辑
src/main.js
完整代码如下:import { app, BrowserWindow, ipcMain, dialog } from 'electron'; import path from 'node:path'; import started from 'electron-squirrel-startup'; // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (started) { app.quit(); } async function handleFileOpen () { const { canceled, filePaths } = await dialog.showOpenDialog() if (!canceled) { return filePaths[0] } } const createWindow = () => { // Create the browser window. const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), }, }); // and load the index.html of the app. if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); } else { mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); } // Open the DevTools. mainWindow.webContents.openDevTools(); }; app.whenReady().then(() => { createWindow(); // Bidirectional communication between rendering process and main process ipcMain.handle('dialog:openFile', handleFileOpen) app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } });
在这个示例中,当用户在 Vue 应用中触发一个打开文件对话框事件时,渲染进程会发送一个 ‘dialog:openFile’ 消息到主进程,主进程接收到消息后会调用 handleFileOpen 函数来处理这个请求,并返回处理结果。
src/preload.js
preload.js
用于上下文隔离;将确保您的预加载脚本
和Electron的内部逻辑
运行在所加载的webcontent网页
之外的另一个独立的上下文环境里。 有助于阻止网站访问Electron 的内部组件和 预加载脚本可访问的高等级权限的API。简而言之就是提供一个入口给渲染进程(前端页面)使用,避免被攻击者随意调用electron内部API。const { contextBridge, ipcRenderer } = require('electron/renderer') contextBridge.exposeInMainWorld('electronAPI', { openFile: () => ipcRenderer.invoke('dialog:openFile'), })
页面示例
通信逻辑实现后,接下来就用一个页面来验证结果
src/vue-project/pages/renderToMain/TwoWay.vue
<template> <div> <button type="button" @click="onClick" id="btn">Open a File</button> <div> File path: <strong>{{ inputVal }}</strong> </div> </div> </template> <script setup> import { ref } from 'vue' const inputVal = ref('') const onClick = async () => { // 调用electron对外暴露的openFile API const filePath = await electronAPI.openFile() inputVal.value = filePath } </script>
src/vue-project/router/index.js
import { createWebHashHistory, createRouter } from 'vue-router' import HomeView from '@/vue-project/pages/home/index.vue' import RenderToMainTwoWay from '@/vue-project/pages/renderToMain/TwoWay.vue' const routes = [ { path: '/', component: HomeView }, // 注册示例路由 { path: '/renderToMainTwoWay', component: RenderToMainTwoWay }, ] const router = createRouter({ history: createWebHashHistory(), routes, }) export default router;
src/vue-project/App.vue
<template> <h1>🖥️ Hello World!</h1> <p>Welcome to your Electron application.</p> <p> <strong>Current route path:</strong> {{ $route.fullPath }} </p> <nav> <div> <RouterLink to="/">Go to Home</RouterLink> </div> <div> <RouterLink to="/renderToMainTwoWay">Render-Process <span style="font-size: 10px;"><--></span> Main-Process </RouterLink> </div> </nav> <div style="margin-top: 20px; border: 1px solid grey; padding: 20px; border-radius: 10px;"> <router-view></router-view> </div> </template> <script setup> </script>