欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你搞懂Electron如何優(yōu)雅的進行進程間通訊

 更新時間:2024年11月20日 09:49:30   作者:若邪  
這篇文章主要為大家詳細介紹了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 字段判斷消息是響應消息(cmdpostMessageCallback)還是普通消息(主進程主動發(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 對象提供了 addEventListenerpostMessage 方法,分別用來監(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ù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論