Electron 進(jìn)程間通信

2023-02-16 17:14 更新

進(jìn)程間通信 (IPC) 是在 Electron 中構(gòu)建功能豐富的桌面應(yīng)用程序的關(guān)鍵部分之一。 由于主進(jìn)程和渲染器進(jìn)程在 Electron 的進(jìn)程模型具有不同的職責(zé),因此 IPC 是執(zhí)行許多常見任務(wù)的唯一方法,例如從 UI 調(diào)用原生 API 或從原生菜單觸發(fā) Web 內(nèi)容的更改。

IPC 通道

在 Electron 中,進(jìn)程使用 ?ipcMain? 和 ?ipcRenderer? 模塊,通過開發(fā)人員定義的“通道”傳遞消息來進(jìn)行通信。 這些通道是 任意 (您可以隨意命名它們)和 雙向 (您可以在兩個(gè)模塊中使用相同的通道名稱)的。

在本指南中,我們將介紹一些基本的 IPC 模式,并提供具體的示例。您可以將這些示例作為您應(yīng)用程序代碼的參考。

了解上下文隔離進(jìn)程

在開始實(shí)現(xiàn)細(xì)節(jié)之前,您應(yīng)該熟悉使用 預(yù)加載腳本 在上下文隔離渲染器進(jìn)程中導(dǎo)入 Node.js 和 Electron 模塊的概念。

  • 有關(guān) Electron 進(jìn)程模型的完整概述,您可以閱讀 流程模型文檔
  • 有關(guān)使用 ?contextBridge? 模塊從預(yù)加載腳本暴露 API 的入門知識(shí),請(qǐng)查看 上下文隔離教程。

模式 1:渲染器進(jìn)程到主進(jìn)程(單向)

要將單向 IPC 消息從渲染器進(jìn)程發(fā)送到主進(jìn)程,您可以使用 ?ipcRenderer.send? API 發(fā)送消息,然后使用 ?ipcMain.on? API 接收。

通常使用此模式從 Web 內(nèi)容調(diào)用主進(jìn)程 API。 我們將通過創(chuàng)建一個(gè)簡(jiǎn)單的應(yīng)用來演示此模式,可以通過編程方式更改它的窗口標(biāo)題。

對(duì)于此演示,您需要將代碼添加到主進(jìn)程、渲染器進(jìn)程和預(yù)加載腳本。 完整代碼如下,我們將在后續(xù)章節(jié)中對(duì)每個(gè)文件進(jìn)行單獨(dú)解釋。

 main.js preload.js  index.html  renderer.js 
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  ipcMain.on('set-title', (event, title) => {
    const webContents = event.sender
    const win = BrowserWindow.fromWebContents(webContents)
    win.setTitle(title)
  })

  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
  
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title) => ipcRenderer.send('set-title', title)
})
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Hello World!</title>
  </head>
  <body>
    Title: <input id="title"/>
    <button id="btn" type="button">Set</button>
    <script src="./renderer.js"></script>
  </body>
</html>
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    const title = titleInput.value
    window.electronAPI.setTitle(title)
});

DOCS/FIDDLES/IPC/PATTERN-1 (22.0.2)

Open in Fiddle

1. 使用 ipcMain.on 監(jiān)聽事件

在主進(jìn)程中,使用 ipcMain.on API 在 set-title 通道上設(shè)置一個(gè) IPC 監(jiān)聽器:

const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')

//...

function handleSetTitle (event, title) {
  const webContents = event.sender
  const win = BrowserWindow.fromWebContents(webContents)
  win.setTitle(title)
}

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  ipcMain.on('set-title', handleSetTitle)
  createWindow()
}
//...

上面的 handleSetTitle 回調(diào)函數(shù)有兩個(gè)參數(shù):一個(gè) ?IpcMainEvent? 結(jié)構(gòu)和一個(gè) title 字符串。 每當(dāng)消息通過 set-title 通道傳入時(shí),此函數(shù)找到附加到消息發(fā)送方的 BrowserWindow 實(shí)例,并在該實(shí)例上使用 win.setTitle API。

INFO

請(qǐng)確保您為以下步驟加載了 index.html 和 preload.js 入口點(diǎn)!

2. 通過預(yù)加載腳本暴露 ipcRenderer.send

要將消息發(fā)送到上面創(chuàng)建的監(jiān)聽器,您可以使用 ipcRenderer.send API。 默認(rèn)情況下,渲染器進(jìn)程沒有權(quán)限訪問 Node.js 和 Electron 模塊。 作為應(yīng)用開發(fā)者,您需要使用 contextBridge API 來選擇要從預(yù)加載腳本中暴露哪些 API。

在您的預(yù)加載腳本中添加以下代碼,向渲染器進(jìn)程暴露一個(gè)全局的 window.electronAPI 變量。

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title) => ipcRenderer.send('set-title', title)
})

此時(shí),您將能夠在渲染器進(jìn)程中使用 window.electronAPI.setTitle() 函數(shù)。

安全警告

出于 安全原因,我們不會(huì)直接暴露整個(gè) ipcRenderer.send API。 確保盡可能限制渲染器對(duì) Electron API 的訪問。

3. 構(gòu)建渲染器進(jìn)程 UI?

在 BrowserWindow 加載的我們的 HTML 文件中,添加一個(gè)由文本輸入框和按鈕組成的基本用戶界面:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Hello World!</title>
  </head>
  <body>
    Title: <input id="title"/>
    <button id="btn" type="button">Set</button>
    <script src="./renderer.js"></script>
  </body>
</html>

為了使這些元素具有交互性,我們將在導(dǎo)入的 renderer.js 文件中添加幾行代碼,以利用從預(yù)加載腳本中暴露的 window.electronAPI 功能:

const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    const title = titleInput.value
    window.electronAPI.setTitle(title)
});

此時(shí),您的演示應(yīng)用應(yīng)該已經(jīng)功能齊全。 嘗試使用輸入框,看看 BrowserWindow 的標(biāo)題會(huì)發(fā)生什么變化!

模式 2:渲染器進(jìn)程到主進(jìn)程(雙向)

雙向 IPC 的一個(gè)常見應(yīng)用是從渲染器進(jìn)程代碼調(diào)用主進(jìn)程模塊并等待結(jié)果。 這可以通過將 ?ipcRenderer.invoke? 與 ?ipcMain.handle? 搭配使用來完成。

在下面的示例中,我們將從渲染器進(jìn)程打開一個(gè)原生的文件對(duì)話框,并返回所選文件的路徑。

對(duì)于此演示,您需要將代碼添加到主進(jìn)程、渲染器進(jìn)程和預(yù)加載腳本。 完整代碼如下,我們將在后續(xù)章節(jié)中對(duì)每個(gè)文件進(jìn)行單獨(dú)解釋。

 main.js preload.js  index.html  renderer.js 
const {app, BrowserWindow, ipcMain, dialog} = require('electron')
const path = require('path')

async function handleFileOpen() {
  const { canceled, filePaths } = await dialog.showOpenDialog()
  if (canceled) {
    return
  } else {
    return filePaths[0]
  }
}

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  ipcMain.handle('dialog:openFile', handleFileOpen)
  createWindow()
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI',{
  openFile: () => ipcRenderer.invoke('dialog:openFile')
})
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Dialog</title>
  </head>
  <body>
    <button type="button" id="btn">Open a File</button>
    File path: <strong id="filePath"></strong>
    <script src='./renderer.js'></script>
  </body>
</html>
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => {
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})

DOCS/FIDDLES/IPC/PATTERN-2 (22.0.2)

Open in Fiddle

1. 使用 ipcMain.handle 監(jiān)聽事件

在主進(jìn)程中,我們將創(chuàng)建一個(gè) handleFileOpen() 函數(shù),它調(diào)用 dialog.showOpenDialog 并返回用戶選擇的文件路徑值。 每當(dāng)渲染器進(jìn)程通過 dialog:openFile 通道發(fā)送 ipcRender.invoke 消息時(shí),此函數(shù)被用作一個(gè)回調(diào)。 然后,返回值將作為一個(gè) Promise 返回到最初的 invoke 調(diào)用。

關(guān)于錯(cuò)誤處理

在主進(jìn)程中通過 handle 引發(fā)的錯(cuò)誤是不透明的,因?yàn)樗鼈儽恍蛄谢耍⑶抑挥性煎e(cuò)誤的 message 屬性會(huì)提供給渲染器進(jìn)程。 詳情請(qǐng)參閱 [#24427](https://github.com/electron/electron/issues/24427)。

const { BrowserWindow, dialog, ipcMain } = require('electron')
const path = require('path')

//...

async function handleFileOpen() {
  const { canceled, filePaths } = await dialog.showOpenDialog()
  if (canceled) {
    return
  } else {
    return filePaths[0]
  }
}

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  mainWindow.loadFile('index.html')
}

app.whenReady(() => {
  ipcMain.handle('dialog:openFile', handleFileOpen)
  createWindow()
})
//...

關(guān)于通道名稱

IPC 通道名稱上的 dialog: 前綴對(duì)代碼沒有影響。 它僅用作命名空間以幫助提高代碼的可讀性。

INFO

請(qǐng)確保您為以下步驟加載了 index.html 和 preload.js 入口點(diǎn)!

2. 通過預(yù)加載腳本暴露 ipcRenderer.invoke

在預(yù)加載腳本中,我們暴露了一個(gè)單行的 openFile 函數(shù),它調(diào)用并返回 ipcRenderer.invoke('dialog:openFile') 的值。 我們將在下一步中使用此 API 從渲染器的用戶界面調(diào)用原生對(duì)話框。

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  openFile: () => ipcRenderer.invoke('dialog:openFile')
})

安全警告

出于 安全原因,我們不會(huì)直接暴露整個(gè) ipcRenderer.invoke API。 確保盡可能限制渲染器對(duì) Electron API 的訪問。

3. 構(gòu)建渲染器進(jìn)程 UI?

最后,讓我們構(gòu)建加載到 BrowserWindow 中的 HTML 文件。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Dialog</title>
  </head>
  <body>
    <button type="button" id="btn">Open a File</button>
    File path: <strong id="filePath"></strong>
    <script src='./renderer.js'></script>
  </body>
</html>

用戶界面包含一個(gè) #btn 按鈕元素,將用于觸發(fā)我們的預(yù)加載 API,以及一個(gè) #filePath 元素,將用于顯示所選文件的路徑。 要使這些部分起作用,需要在渲染器進(jìn)程腳本中編寫幾行代碼:

const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => {
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})

在上面的代碼片段中,我們監(jiān)聽 #btn 按鈕的點(diǎn)擊,并調(diào)用 window.electronAPI.openFile() API 來激活原生的打開文件對(duì)話框。 然后我們?cè)?nbsp;#filePath 元素中顯示選中文件的路徑。

注意:對(duì)于舊方法

ipcRenderer.invoke API 是在 Electron 7 中添加的,作為處理渲染器進(jìn)程中雙向 IPC 的一種開發(fā)人員友好的方式。 但這種 IPC 模式存在幾種替代方法。

如果可能,請(qǐng)避免使用舊方法

我們建議盡可能使用 ipcRenderer.invoke 。 出于保留歷史的目地,記錄了下面雙向地渲染器到主進(jìn)程模式。

INFO

對(duì)于以下示例,我們將直接從預(yù)加載腳本調(diào)用 ipcRenderer,以保持代碼示例短小。

使用 ipcRenderer.send

我們用于單向通信的 ipcRenderer.send API 也可用于雙向通信。 這是在 Electron 7 之前通過 IPC 進(jìn)行異步雙向通信的推薦方式。

// 您也可以使用 `contextBridge` API
// 將這段代碼暴露給渲染器進(jìn)程
const { ipcRenderer } = require('electron')

ipcRenderer.on('asynchronous-reply', (_event, arg) => {
  console.log(arg) // 在 DevTools 控制臺(tái)中打印“pong”
})
ipcRenderer.send('asynchronous-message', 'ping')
ipcMain.on('asynchronous-message', (event, arg) => {
  console.log(arg) // 在 Node 控制臺(tái)中打印“ping”
  // 作用如同 `send`,但返回一個(gè)消息
  // 到發(fā)送原始消息的渲染器
  event.reply('asynchronous-reply', 'pong')
})

這種方法有幾個(gè)缺點(diǎn):

  • 您需要設(shè)置第二個(gè) ipcRenderer.on 監(jiān)聽器來處理渲染器進(jìn)程中的響應(yīng)。 使用 invoke,您將獲得作為 Promise 返回到原始 API 調(diào)用的響應(yīng)值。
  • 沒有顯而易見的方法可以將 asynchronous-reply 消息與原始的 asynchronous-message 消息配對(duì)。 如果您通過這些通道非常頻繁地來回傳遞消息,則需要添加其他應(yīng)用代碼來單獨(dú)跟蹤每個(gè)調(diào)用和響應(yīng)。

使用 ipcRenderer.sendSync

ipcRenderer.sendSync API 向主進(jìn)程發(fā)送消息,并 同步 等待響應(yīng)。

const { ipcMain } = require('electron')
ipcMain.on('synchronous-message', (event, arg) => {
  console.log(arg) // 在 Node 控制臺(tái)中打印“ping”
  event.returnValue = 'pong'
})
// 您也可以使用 `contextBridge` API
// 將這段代碼暴露給渲染器進(jìn)程
const { ipcRenderer } = require('electron')

const result = ipcRenderer.sendSync('synchronous-message', 'ping')
console.log(result) // 在 DevTools 控制臺(tái)中打印“pong”

這份代碼的結(jié)構(gòu)與 invoke 模型非常相似,但出于性能原因,我們建議避免使用此 API。 它的同步特性意味著它將阻塞渲染器進(jìn)程,直到收到回復(fù)為止。

模式 3:主進(jìn)程到渲染器進(jìn)程

將消息從主進(jìn)程發(fā)送到渲染器進(jìn)程時(shí),需要指定是哪一個(gè)渲染器接收消息。 消息需要通過其 WebContents 實(shí)例發(fā)送到渲染器進(jìn)程。 此 WebContents 實(shí)例包含一個(gè) send 方法,其使用方式與 ipcRenderer.send 相同。

為了演示此模式,我們將構(gòu)建一個(gè)由原生操作系統(tǒng)菜單控制的數(shù)字計(jì)數(shù)器。

對(duì)于此演示,您需要將代碼添加到主進(jìn)程、渲染器進(jìn)程和預(yù)加載腳本。 完整代碼如下,我們將在后續(xù)章節(jié)中對(duì)每個(gè)文件進(jìn)行單獨(dú)解釋。

 main.js preload.js  index.html  renderer.js 
const {app, BrowserWindow, Menu, ipcMain} = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  const menu = Menu.buildFromTemplate([
    {
      label: app.name,
      submenu: [
      {
        click: () => mainWindow.webContents.send('update-counter', 1),
        label: 'Increment',
      },
      {
        click: () => mainWindow.webContents.send('update-counter', -1),
        label: 'Decrement',
      }
      ]
    }

  ])

  Menu.setApplicationMenu(menu)
  mainWindow.loadFile('index.html')

  // Open the DevTools.
  mainWindow.webContents.openDevTools()
}

app.whenReady().then(() => {
  ipcMain.on('counter-value', (_event, value) => {
    console.log(value) // will print value to Node console
  })
  createWindow()
  
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  handleCounter: (callback) => ipcRenderer.on('update-counter', callback)
})
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Menu Counter</title>
  </head>
  <body>
    Current value: <strong id="counter">0</strong>
    <script src="./renderer.js"></script>
  </body>
</html>
const counter = document.getElementById('counter')

window.electronAPI.handleCounter((event, value) => {
    const oldValue = Number(counter.innerText)
    const newValue = oldValue + value
    counter.innerText = newValue
    event.sender.send('counter-value', newValue)
})

DOCS/FIDDLES/IPC/PATTERN-3 (22.0.2)

Open in Fiddle

1. 使用 webContents 模塊發(fā)送消息

對(duì)于此演示,我們需要首先使用 Electron 的 Menu 模塊在主進(jìn)程中構(gòu)建一個(gè)自定義菜單,該模塊使用 webContents.send API 將 IPC 消息從主進(jìn)程發(fā)送到目標(biāo)渲染器。

const {app, BrowserWindow, Menu, ipcMain} = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  const menu = Menu.buildFromTemplate([
    {
      label: app.name,
      submenu: [
        {
          click: () => mainWindow.webContents.send('update-counter', 1),
          label: 'Increment',
        },
        {
          click: () => mainWindow.webContents.send('update-counter', -1),
          label: 'Decrement',
        }
      ]
    }
  ])
  Menu.setApplicationMenu(menu)

  mainWindow.loadFile('index.html')
}
//...

出于本教程的目的,請(qǐng)務(wù)必注意, click 處理函數(shù)通過 update-counter 通道向渲染器進(jìn)程發(fā)送消息(1 或 -1)。

click: () => mainWindow.webContents.send('update-counter', -1)

INFO

請(qǐng)確保您為以下步驟加載了 index.html 和 preload.js 入口點(diǎn)!

2. 通過預(yù)加載腳本暴露 ipcRenderer.on

與前面的渲染器到主進(jìn)程的示例一樣,我們使用預(yù)加載腳本中的 contextBridge 和 ipcRenderer 模塊向渲染器進(jìn)程暴露 IPC 功能:

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    onUpdateCounter: (callback) => ipcRenderer.on('update-counter', callback)
})

加載預(yù)加載腳本后,渲染器進(jìn)程應(yīng)有權(quán)訪問 window.electronAPI.onUpdateCounter() 監(jiān)聽器函數(shù)。

安全警告

出于 安全原因,我們不會(huì)直接暴露整個(gè) ipcRenderer.on API。 確保盡可能限制渲染器對(duì) Electron API 的訪問。

INFO

在這個(gè)最小示例中,您可以直接在預(yù)加載腳本中調(diào)用 ipcRenderer.on ,而不是通過 context bridge 暴露它。

const { ipcRenderer } = require('electron')

window.addEventListener('DOMContentLoaded', () => {
    const counter = document.getElementById('counter')
    ipcRenderer.on('update-counter', (_event, value) => {
        const oldValue = Number(counter.innerText)
        const newValue = oldValue + value
        counter.innerText = newValue
    })
})

但是,與通過 context bridge 暴露預(yù)加載 API 相比,此方法的靈活性有限,因?yàn)楸O(jiān)聽器無法直接與渲染器代碼交互。

3. 構(gòu)建渲染器進(jìn)程 UI

為了將它們聯(lián)系在一起,我們將在加載的 HTML 文件中創(chuàng)建一個(gè)接口,其中包含一個(gè) #counter 元素,我們將使用該元素來顯示值:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Menu Counter</title>
  </head>
  <body>
    Current value: <strong id="counter">0</strong>
    <script src="./renderer.js"></script>
  </body>
</html>

最后,為了更新 HTML 文檔中的值,我們將添加幾行 DOM 操作的代碼,以便在每次觸發(fā) update-counter 事件時(shí)更新 #counter 元素的值。

const counter = document.getElementById('counter')

window.electronAPI.onUpdateCounter((_event, value) => {
    const oldValue = Number(counter.innerText)
    const newValue = oldValue + value
    counter.innerText = newValue
})

在上面的代碼中,我們將回調(diào)傳遞給從預(yù)加載腳本中暴露的 window.electronAPI.onUpdateCounter 函數(shù)。 第二個(gè) value 參數(shù)對(duì)應(yīng)于我們傳入 webContents.send 函數(shù)的 1 或 -1,該函數(shù)是從原生菜單調(diào)用的。

可選:返回一個(gè)回復(fù)

對(duì)于從主進(jìn)程到渲染器進(jìn)程的 IPC,沒有與 ipcRenderer.invoke 等效的 API。 不過,您可以從 ipcRenderer.on 回調(diào)中將回復(fù)發(fā)送回主進(jìn)程。

我們可以對(duì)前面例子的代碼進(jìn)行略微修改來演示這一點(diǎn)。 在渲染器進(jìn)程中,使用 event 參數(shù),通過 counter-value 通道將回復(fù)發(fā)送回主進(jìn)程。

const counter = document.getElementById('counter')

window.electronAPI.onUpdateCounter((event, value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue
  event.sender.send('counter-value', newValue)
})

在主進(jìn)程中,監(jiān)聽 counter-value 事件并適當(dāng)?shù)靥幚硭鼈儭?

//...
ipcMain.on('counter-value', (_event, value) => {
  console.log(value) // 將打印到 Node 控制臺(tái)
})
//...

模式 4:渲染器進(jìn)程到渲染器進(jìn)程

沒有直接的方法可以使用 ipcMain 和 ipcRenderer 模塊在 Electron 中的渲染器進(jìn)程之間發(fā)送消息。 為此,您有兩種選擇:

  • 將主進(jìn)程作為渲染器之間的消息代理。 這需要將消息從一個(gè)渲染器發(fā)送到主進(jìn)程,然后主進(jìn)程將消息轉(zhuǎn)發(fā)到另一個(gè)渲染器。
  • 從主進(jìn)程將一個(gè) ?MessagePort? 傳遞到兩個(gè)渲染器。 這將允許在初始設(shè)置后渲染器之間直接進(jìn)行通信。

對(duì)象序列化?

Electron 的 IPC 實(shí)現(xiàn)使用 HTML 標(biāo)準(zhǔn)的 結(jié)構(gòu)化克隆算法 來序列化進(jìn)程之間傳遞的對(duì)象,這意味著只有某些類型的對(duì)象可以通過 IPC 通道傳遞。

特別是 DOM 對(duì)象(例如 Element,Location 和 DOMMatrix),Node.js 中由 C++ 類支持的對(duì)象(例如 process.envStream 的一些成員)和 Electron 中由 C++ 類支持的對(duì)象(例如 WebContents、BrowserWindow 和 WebFrame)無法使用結(jié)構(gòu)化克隆序列化。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)