一文帶你搞懂Electron如何優(yōu)雅的進行進程間通訊
Electron 本身提供的進程間通訊的方法比較偏底層,使用起來有些繁瑣、不易維護。Electron 中渲染進程和主進程之間的通訊,其實就像傳統(tǒng) web 開發(fā)中網(wǎng)頁端與服務器的通訊。那么 Electron 中進程間通訊能不能實現(xiàn)像調(diào)用 HTTP 請求一樣方便呢,答案是肯定的。
以下是一個 HTTP 請求的簡單封裝:
import { request } from "@/utils/request"; const { VITE_HOST } = import.meta.env; // #region 操作密碼校驗接口 export interface ICheckPwdResult { /** * 0:成功,其他:失敗 */ code: string; message: string; } export interface ICheckPwdData { /** * 類型:LOCK_SCREEN:鎖屏 */ type: string; /** * 密碼 */ password: string; } /** * 操作密碼校驗接口 * /project/2205/interface/api/378926 * @author * * @param {ICheckPwdData} data * @returns */ export function checkPwd(data: ICheckPwdData) { return request<ICheckPwdResult>({ url: `${VITE_HOST}/user/operation/pwd/check`, method: "POST", data, }); } // #endregion
下面是 Electron 中渲染進程向主進程發(fā)送請求的封裝:
export const setMaximize = () => { return request({ cmd: "setMaximize", }); }; export const setMinimize = () => { return request({ cmd: "setMinimize", }); }; export const closeWindow = () => { return request({ cmd: "closeWindow", }); }; /** * @description 獲取 mac 地址 * @returns */ export const getMac = () => { return request<string>({ cmd: "getMac", }); };
下面細說如何封裝:
Electron 進程間通訊有四種模式:
- 渲染器進程到主進程(單向)
- 渲染器進程到主進程(雙向)
- 主進程到渲染器進程(單向)
- 渲染器進程到渲染器進程
更多細節(jié)可以查看 進程間通信 | Electron
單向和雙向的區(qū)別就是:單向的消息發(fā)出去之后是沒有消息返回的,或者說拿不到返回的消息;雙向就是請求發(fā)出后可以拿到響應信息,就跟 HTTP 請求一樣。
舉個例子,頁面向主進程發(fā)起請求,希望拿到計算機的 mac 地址,主進程通過調(diào)用相應的 nodejs api 拿到 mac 地址返回,頁面拿到消息結(jié)果,這就是雙向的。
我并沒有用到原生就支持雙向的 2 模式,而是使用都是單向的 1、3 模式,通過封裝后實現(xiàn)雙向的效果。主要是因為 Electron 并不支持主進程到渲染器進程的雙向模式,也就是主進程給頁面主動發(fā)送消息后是無法拿到響應的。為了保持封裝后代碼的統(tǒng)一性,都使用了單向的模式進行封裝。
結(jié)合下圖以及實際代碼調(diào)用,說說是如何基于單向的消息模式實現(xiàn)雙向通訊的。
/** * @description 獲取 mac 地址 * @returns */ export const getMac = () => { return request<string>({ cmd: "getMac", }); };
const handleGetMac = () => { getMac().then((mac) => { model.mac.value = mac; }); };
頁面中通過調(diào)用getMac
方法獲取計算機 mac 地址。
getMac
方法是一個異步方法,渲染進程在調(diào)用這個方法發(fā)送消息的時候,會生成一個 uuid,然后將 uuid 、cmd 字段、data 數(shù)據(jù)(這里沒有)放到消息體里再發(fā)送給主進程。同時會在回調(diào)隊列里增加兩條回調(diào),一條成功回調(diào),一條失敗回調(diào),并且使用 uuid 標識,成功回調(diào)就是 then 里面的函數(shù),失敗回調(diào)就是 catch 里的函數(shù)。
主進程接收到消息后,根據(jù)cmd
字段做相應的處理,處理完后將結(jié)果
和渲染進程發(fā)送的消息體里的 uuid
放到響應消息體里,響應消息體里也有 cmd
字段,并且是固定(我使用postMessageCallback
標識),這樣渲染進程在接收到消息的時候就知道這是一條之前發(fā)送出去的消息的響應。響應消息體里還有一個code
字段標識是成功還是失敗。
渲染進程接收到消息后,根據(jù)消息體里的 cmd
字段判斷消息是響應消息(cmd
為postMessageCallback
)還是普通消息(主進程主動發(fā)送的消息)。如果是響應消息,根據(jù) uuid
在回調(diào)隊列里找到相應的回調(diào),再根據(jù) code
判斷是執(zhí)行成功回調(diào)還是失敗回調(diào)。
以上就是渲染進程到主進程的單向消息模式實現(xiàn)雙向通訊的整個通訊過程。把消息的發(fā)送、處理、返回響應的對象逆過來就是主進程到渲染進程的雙向通訊了。
原理說清楚了,下面就是代碼實現(xiàn)了。
渲染進程需要有發(fā)送消息、監(jiān)聽消息的功能:
contextBridge.exposeInMainWorld("ipcRenderer", { addEventListener( key: "message", listener: (data: { cmd: string; cbid: string; data: unknown }) => void, ) { return ipcRenderer.on(key, (...args) => { const message = args[1] as { cmd: string; cbid: string; data: unknown }; listener(message); }); }, postMessage(data: { cmd: string; data: unknown; cdid?: string; code?: number; }) { return ipcRenderer.send("message", data); },
這樣頁面中 window 對象里就有了 ipcRenderer
對象,ipcRenderer
對象提供了 addEventListener
、postMessage
方法,分別用來監(jiān)聽消息和發(fā)送消息。
把 addEventListener
、postMessage
再做一下封裝,就可以像調(diào)用 HTTP 請求一樣向主進程發(fā)起請求了。
/* eslint-disable no-shadow */ import handle from "./handle"; const callbacks: { [propName: string]: (data: unknown) => void } = {}; const errorCallbacks: { [propName: string]: (data: unknown) => void } = {}; function postMessage( data: { cmd: string; data?: unknown }, cb?: (data: unknown) => void, errorCb?: (data: unknown) => void, ) { if (cb) { const cbid = Date.now().toString(); callbacks[cbid] = cb; window.ipcRenderer?.postMessage({ cmd: data.cmd, data: data.data, cbid: cbid, }); if (errorCb) { errorCallbacks[cbid] = errorCb; } } else { window.ipcRenderer?.postMessage({ cmd: data.cmd, data: data.data, }); } } function request<T = unknown>(params: { cmd: string; data?: unknown }) { return new Promise<T>((resolve, reject) => { postMessage( { cmd: params.cmd, data: params.data }, (res) => { resolve(res as T); }, (error) => { reject(error); }, ); }); } function invokeCallback<T = unknown>(cbid: string, res: T) { window.ipcRenderer?.postMessage({ cmd: "postMessageCallback", cbid, data: res, code: 200, }); } function invokeErrorCallback(cbid: string, res: unknown) { window.ipcRenderer?.postMessage({ cmd: "postMessageCallback", cbid, data: res, code: 400, }); } export const addIpcRendererEventListener = () => { window.ipcRenderer?.addEventListener("message", async (message) => { console.log("ipcRenderer get message", message); // 處理主進程主動發(fā)的消息 if (message.cmd !== "postMessageCallback") { if (handle[message.cmd]) { try { const res = await handle[message.cmd](message.data); invokeCallback(message.cbid, res); } catch (ex: unknown) { invokeErrorCallback(message.cbid, ex); } } else { invokeErrorCallback(message.cbid, `方法不存在:${message.cmd}`); } } // 處理回調(diào) else { if (message.code === 200) { (callbacks[message.cbid] || function () {})(message.data); } else { (errorCallbacks[message.cbid] || function () {})(message.data); } delete callbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除 delete errorCallbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除 } }); }; /** * @description 獲取 mac 地址 * @returns */ export const getMac = () => { return request<string>({ cmd: "getMac", }); };
下面是主進程的代碼封裝:
import { ipcMain } from "electron"; import handle from "./handle"; /* eslint-disable no-shadow */ const callbacks: { [propName: string]: (data: unknown) => void } = {}; const errorCallbacks: { [propName: string]: (data: unknown) => void } = {}; function postMessage( webContents: Electron.WebContents, data: { cmd: string; data?: unknown }, cb?: (data: unknown) => void, errorCb?: (data: unknown) => void, ) { if (cb) { const cbid = Date.now().toString(); callbacks[cbid] = cb; webContents.send("message", { cmd: data.cmd, data: data.data, cbid: cbid, }); if (errorCb) { errorCallbacks[cbid] = errorCb; } } else { webContents.send("message", { cmd: data.cmd, data: data.data, }); } } function request<T = unknown>( webContents: Electron.WebContents, params: { cmd: string; data?: unknown }, ) { return new Promise<T>((resolve, reject) => { postMessage( webContents, { cmd: params.cmd, data: params.data }, (res) => { resolve(res as T); }, (error) => { reject(error); }, ); }); } function invokeCallback<T = unknown>( webContents: Electron.WebContents, cbid: string, res: T, ) { webContents.send("message", { cmd: "postMessageCallback", cbid, data: res, code: 200, }); } function invokeErrorCallback( webContents: Electron.WebContents, cbid: string, res: unknown, ) { webContents.send("message", { cmd: "postMessageCallback", cbid, data: res, code: 400, }); } export const addIpcMainEventListener = () => { ipcMain.on( "message", async ( event, message: { cmd: string; cbid: string; data: unknown; code?: number }, ) => { // 處理渲染進程主動發(fā)的消息 if (message.cmd !== "postMessageCallback") { if (handle[message.cmd]) { try { const res = await handle[message.cmd](event, message.data); invokeCallback(event.sender, message.cbid, res); } catch (ex: unknown) { invokeErrorCallback(event.sender, message.cbid, ex); } } else { invokeErrorCallback( event.sender, message.cbid, `方法不存在:${message.cmd}`, ); } } // 處理發(fā)出去的請求的回調(diào) else { if (message.code === 200) { (callbacks[message.cbid] || function () {})(message.data); } else { (errorCallbacks[message.cbid] || function () {})(message.data); } delete callbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除 delete errorCallbacks[message.cbid]; // 執(zhí)行完回調(diào)刪除 } }, ); }; // 主進程向 ipcRenderer 發(fā)起關閉請求,ipcRenderer 彈框確認,ipcRenderer 再通知主進程關閉窗口 export const closeWindow = (webContents: Electron.WebContents) => { return request(webContents, { cmd: "closeWindow", }); };
主進程處理消息:
import getMAC from "../getmac"; const handle: Record< string, // eslint-disable-next-line @typescript-eslint/no-explicit-any (event: Electron.IpcMainEvent, data: any) => void > = { getMac: () => { let mac = ""; try { mac = getMAC(); } catch (ex) { console.log(ex); } return mac; }, }; export default handle;
封裝完之后,進程間的通訊就像發(fā)起 HTTP 請求一樣簡單
const handleGetMac = () => { getMac().then((mac) => { model.mac.value = mac; }); };
到此這篇關于一文帶你搞懂Electron如何優(yōu)雅的進行進程間通訊的文章就介紹到這了,更多相關Electron進程間通訊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
uni-app自定義導航欄右側(cè)做增加按鈕并跳轉(zhuǎn)鏈接功能
這篇文章主要介紹了uni-app自定義導航欄右側(cè)做增加按鈕并跳轉(zhuǎn)鏈接,本文通過實例代碼給大家分享實現(xiàn)思路,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03javascript+html5實現(xiàn)繪制圓環(huán)的方法
這篇文章主要介紹了javascript+html5實現(xiàn)繪制圓環(huán)的方法,實例分析了javascript實現(xiàn)html5基于canvas繪制圓環(huán)的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07JS數(shù)組實現(xiàn)分類統(tǒng)計實例代碼
本文通過實例代碼給大家介紹了js數(shù)組實現(xiàn)分類統(tǒng)計的相關知識,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-09-09瀏覽器JavaScript調(diào)試功能無法使用解決方案
這篇文章主要介紹了瀏覽器JavaScript調(diào)試功能無法使用解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-09-09