一文帶你搞懂Electron如何優(yōu)雅的進(jìn)行進(jìn)程間通訊
Electron 本身提供的進(jìn)程間通訊的方法比較偏底層,使用起來有些繁瑣、不易維護(hù)。Electron 中渲染進(jìn)程和主進(jìn)程之間的通訊,其實就像傳統(tǒng) web 開發(fā)中網(wǎng)頁端與服務(wù)器的通訊。那么 Electron 中進(jìn)程間通訊能不能實現(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 中渲染進(jìn)程向主進(jìn)程發(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",
});
};
下面細(xì)說如何封裝:
Electron 進(jìn)程間通訊有四種模式:
- 渲染器進(jìn)程到主進(jìn)程(單向)
- 渲染器進(jìn)程到主進(jìn)程(雙向)
- 主進(jìn)程到渲染器進(jìn)程(單向)
- 渲染器進(jìn)程到渲染器進(jìn)程
更多細(xì)節(jié)可以查看 進(jìn)程間通信 | Electron
單向和雙向的區(qū)別就是:單向的消息發(fā)出去之后是沒有消息返回的,或者說拿不到返回的消息;雙向就是請求發(fā)出后可以拿到響應(yīng)信息,就跟 HTTP 請求一樣。
舉個例子,頁面向主進(jìn)程發(fā)起請求,希望拿到計算機的 mac 地址,主進(jìn)程通過調(diào)用相應(yīng)的 nodejs api 拿到 mac 地址返回,頁面拿到消息結(jié)果,這就是雙向的。
我并沒有用到原生就支持雙向的 2 模式,而是使用都是單向的 1、3 模式,通過封裝后實現(xiàn)雙向的效果。主要是因為 Electron 并不支持主進(jìn)程到渲染器進(jìn)程的雙向模式,也就是主進(jìn)程給頁面主動發(fā)送消息后是無法拿到響應(yīng)的。為了保持封裝后代碼的統(tǒng)一性,都使用了單向的模式進(jìn)行封裝。
結(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方法是一個異步方法,渲染進(jìn)程在調(diào)用這個方法發(fā)送消息的時候,會生成一個 uuid,然后將 uuid 、cmd 字段、data 數(shù)據(jù)(這里沒有)放到消息體里再發(fā)送給主進(jìn)程。同時會在回調(diào)隊列里增加兩條回調(diào),一條成功回調(diào),一條失敗回調(diào),并且使用 uuid 標(biāo)識,成功回調(diào)就是 then 里面的函數(shù),失敗回調(diào)就是 catch 里的函數(shù)。
主進(jìn)程接收到消息后,根據(jù)cmd 字段做相應(yīng)的處理,處理完后將結(jié)果和渲染進(jìn)程發(fā)送的消息體里的 uuid 放到響應(yīng)消息體里,響應(yīng)消息體里也有 cmd 字段,并且是固定(我使用postMessageCallback標(biāo)識),這樣渲染進(jìn)程在接收到消息的時候就知道這是一條之前發(fā)送出去的消息的響應(yīng)。響應(yīng)消息體里還有一個code 字段標(biāo)識是成功還是失敗。
渲染進(jìn)程接收到消息后,根據(jù)消息體里的 cmd 字段判斷消息是響應(yīng)消息(cmd為postMessageCallback)還是普通消息(主進(jìn)程主動發(fā)送的消息)。如果是響應(yīng)消息,根據(jù) uuid 在回調(diào)隊列里找到相應(yīng)的回調(diào),再根據(jù) code 判斷是執(zhí)行成功回調(diào)還是失敗回調(diào)。
以上就是渲染進(jìn)程到主進(jìn)程的單向消息模式實現(xiàn)雙向通訊的整個通訊過程。把消息的發(fā)送、處理、返回響應(yīng)的對象逆過來就是主進(jìn)程到渲染進(jìn)程的雙向通訊了。
原理說清楚了,下面就是代碼實現(xiàn)了。
渲染進(jì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 請求一樣向主進(jìn)程發(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);
// 處理主進(jìn)程主動發(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",
});
};
下面是主進(jìn)程的代碼封裝:
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 },
) => {
// 處理渲染進(jìn)程主動發(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)刪除
}
},
);
};
// 主進(jìn)程向 ipcRenderer 發(fā)起關(guān)閉請求,ipcRenderer 彈框確認(rèn),ipcRenderer 再通知主進(jìn)程關(guān)閉窗口
export const closeWindow = (webContents: Electron.WebContents) => {
return request(webContents, {
cmd: "closeWindow",
});
};
主進(jìn)程處理消息:
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;
封裝完之后,進(jìn)程間的通訊就像發(fā)起 HTTP 請求一樣簡單
const handleGetMac = () => {
getMac().then((mac) => {
model.mac.value = mac;
});
};

到此這篇關(guān)于一文帶你搞懂Electron如何優(yōu)雅的進(jìn)行進(jìn)程間通訊的文章就介紹到這了,更多相關(guān)Electron進(jìn)程間通訊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
uni-app自定義導(dǎo)航欄右側(cè)做增加按鈕并跳轉(zhuǎn)鏈接功能
這篇文章主要介紹了uni-app自定義導(dǎo)航欄右側(cè)做增加按鈕并跳轉(zhuǎn)鏈接,本文通過實例代碼給大家分享實現(xiàn)思路,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
javascript+html5實現(xiàn)繪制圓環(huán)的方法
這篇文章主要介紹了javascript+html5實現(xiàn)繪制圓環(huán)的方法,實例分析了javascript實現(xiàn)html5基于canvas繪制圓環(huán)的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07
JS數(shù)組實現(xiàn)分類統(tǒng)計實例代碼
本文通過實例代碼給大家介紹了js數(shù)組實現(xiàn)分類統(tǒng)計的相關(guān)知識,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-09-09
javascript 數(shù)組去重復(fù)(在線去重工具)
很多情況下我們需要去掉重復(fù)的內(nèi)容,一般我們都是將很多內(nèi)容放到一個數(shù)組里面,然后再去重復(fù),這里簡單為大家整理一下2016-12-12
瀏覽器JavaScript調(diào)試功能無法使用解決方案
這篇文章主要介紹了瀏覽器JavaScript調(diào)試功能無法使用解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09

