Skip to content

Electron官方文档Electron中文文档

Electron集成发布包可以直接用dist下的html即loadFile,如果本地测试+VUE,则可以loadUrl,实际与PyWebview类似,均可以搭配一个嵌入式服务运行程序,也就是套壳。

推荐用这个模版集成

实际上这个模版只是帮助我们把Vite和Electron整合了而已,实际开发是可以分离的,比如我更倾向于使用uni-app开发,这样可以同时兼顾微信小程序,PC网站,PC客户端,把API部分的实现单独隔离出来,比如网站和小程序使用Serverless,PC使用Electron或者Python。

Electron网络请求对比

推荐使用原生的net模块

ts
// Issue HTTP/HTTPS requests using Chromium's native networking library.
//
// The net module is a client-side API for issuing HTTP(S) requests. It
// is similar to the HTTP and HTTPS modules of Node.js but uses Chromium's
// native networking library instead of the Node.js implementation, offering
// better support for web proxies.
//
// For more info, see:
// https://electronjs.org/docs/api/net

const { app, net } = require('electron')

app.whenReady().then(() => {
  const request = net.request('https://github.com')

  request.on('response', (response) => {
    console.log(`STATUS: ${response.statusCode}`)
    console.log(`HEADERS: ${JSON.stringify(response.headers)}`)

    response.on('data', (chunk) => {
      console.log(`BODY: ${chunk}`)
    })

    response.on('end', () => {
      console.log('No more data in the response.')
    })
  })

  request.end()
})
// Issue HTTP/HTTPS requests using Chromium's native networking library.
//
// The net module is a client-side API for issuing HTTP(S) requests. It
// is similar to the HTTP and HTTPS modules of Node.js but uses Chromium's
// native networking library instead of the Node.js implementation, offering
// better support for web proxies.
//
// For more info, see:
// https://electronjs.org/docs/api/net

const { app, net } = require('electron')

app.whenReady().then(() => {
  const request = net.request('https://github.com')

  request.on('response', (response) => {
    console.log(`STATUS: ${response.statusCode}`)
    console.log(`HEADERS: ${JSON.stringify(response.headers)}`)

    response.on('data', (chunk) => {
      console.log(`BODY: ${chunk}`)
    })

    response.on('end', () => {
      console.log('No more data in the response.')
    })
  })

  request.end()
})

Electron提供了多种方式进行网络请求,包括使用Node.js的 http/https 模块、第三方库(如 axiosnode-fetch)以及Electron自己的 net 模块。这些方法之间存在一些关键的区别和优势:

1. Node.js 的 http/https 模块

  • 优势:作为Node.js的核心模块,这些模块不需要额外安装,且与Node.js环境高度集成。
  • 限制:不支持某些Chromium特有的网络功能,如自动处理代理服务器配置、使用Chromium的缓存等。

2. 第三方库(如 axiosnode-fetch

  • 优势:提供更简洁、现代的API。很多库支持Promise,使得异步代码更易于编写和维护。
  • 限制:需要额外安装这些库。它们通常依赖于Node.js的网络实现,并不一定能完全利用Chromium的网络特性。

3. Electron 的 net 模块

  • 优势
    • 使用Chromium的网络栈,提供对web代理和其他浏览器级别网络特性的支持。
    • 可以利用Chromium的缓存机制,提高性能。
    • 更好地与Electron的其他浏览器功能集成,如处理Cookies、HTTP/2支持等。
  • 限制
    • API可能不如Node.js的 http/https 或某些第三方库那么熟悉。
    • 只在Electron环境中可用,不适合那些需要在纯Node.js环境下运行的代码。

总体来说,如果你需要更紧密地集成Chromium的网络特性,或者你的应用依赖于Electron的浏览器环境(如处理复杂的代理配置、Cookie管理等),使用 net 模块可能是更好的选择。否则,Node.js的 http/https 或者像 axios 这样的第三方库可能会更简单、更方便。下面是对比的继续:

使用场景和适应性

  • Node.js http/https

    • 当需要Node.js环境的兼容性(例如,代码同时用于Electron和服务器端)时,这些模块是好的选择。
    • 适合那些需要直接控制底层网络请求细节的场景。
  • 第三方库(如 axiosnode-fetch

    • 当你需要更现代、简洁的API,以及额外的功能(如拦截器、转换响应等)时。
    • 当你的项目已经在使用这些库,或者团队对它们更熟悉时。
  • Electron net 模块

    • 当应用需要紧密集成Electron环境,利用Chromium的网络栈的特性(如更好的代理支持、HTTP/2等)时。
    • 当你的应用主要运行在Electron环境中,并且你想最大限度地利用Electron提供的功能时。

性能和效率

  • Electron net 模块 由于使用Chromium的网络栈,可能在处理某些网络请求时更高效,特别是在涉及到复杂的浏览器级功能(如处理Cookies、代理设置)时。

  • Node.js http/https第三方库 在普通的HTTP请求处理上可能没有明显的性能差异,但在特定场景下(如大量数据传输、高并发请求)可能需要特别优化。

总结

选择哪种方法取决于你的具体需求、应用的架构以及开发团队的熟悉程度。如果你的应用需要紧密集成Electron和Chromium的特性,net 模块是一个很好的选择。如果你更倾向于使用更现代的API或者需要更好的Node.js兼容性,那么 http/https 模块或第三方库可能更合适。

BrowserView与BrowserWindow.loadURL相比

BrowserView 是 Electron 中的一个高级功能,它提供了一种将多个独立的 web 内容嵌入到同一个窗口的能力。虽然使用 BrowserWindowloadURLloadFile 方法可以加载单个页面,但 BrowserView 使得在单个 BrowserWindow 中同时展示多个 web 页面或视图成为可能。下面是 BrowserView 的一些关键用途和优点:

1. 多视图布局

  • BrowserView 允许你在同一个窗口中布置多个独立渲染和控制的 web 视图。
  • 这对于需要同时显示多个网页或应用部分的复杂应用特别有用,例如一个集成了多个服务的仪表盘。

2. 更灵活的控制

  • BrowserView 可以被动态创建、销毁、隐藏和显示,提供了比单一页面更多的灵活性。
  • 它允许开发者对每个视图进行更精细的控制,比如独立设置视图大小、位置和 z-index。

3. 隔离和安全

  • 不同的 BrowserView 可以彼此隔离,提高应用的安全性和稳定性。
  • 如果一个 BrowserView 崩溃,它不会影响到应用中的其他部分。

4. 性能考虑

  • 对于复杂的应用界面,使用多个 BrowserView 可以比使用单个 BrowserWindow 加载多个 iframe 更有效率。
  • 它允许每个视图独立管理和优化,有助于提高整体应用性能。

使用示例

一个典型的使用场景可能是一个集成了多个独立功能模块的应用,比如一个同时显示聊天界面、电子邮件列表和日历的办公应用。每个部分可以是一个独立的 BrowserView,允许用户在不切换窗口的情况下交互和查看不同的信息。

总的来说,BrowserView 提供了一种在单个 Electron 窗口中展示和管理多个 web 视图的强大方法。它对于开发复杂和高度交互的桌面应用特别有用。

IPC通信-ipcRenderer.invoke和ipcRenderer.send区别

IPC单向通信与双向通信

省流结论:

  1. ipcRenderer.invoke 与 ipcMain.handle 搭配使用实现双向通信。
  2. ipcRenderer.send和ipcMain.on 搭配使用实现单向通信。当然,也可以实现双向通信,只不过要通过event事件形式event.sender.send('counter-value', newValue),官方不建议。因为事件通常用于有来无回比较合适。

main.ts,比如下面这样也是也可以双向通信的,但是不是一个最佳方式。

js
ipcMain.on('open-file-dialog', (event) => {
  dialog.showOpenDialog({
    properties: ['openFile']
  }).then(result => {
    if (!result.canceled && result.filePaths.length > 0) {
      event.sender.send('selected-file', result.filePaths[0]);
    }
  }).catch(err => {
    console.log(err);
  });
});
ipcMain.on('open-file-dialog', (event) => {
  dialog.showOpenDialog({
    properties: ['openFile']
  }).then(result => {
    if (!result.canceled && result.filePaths.length > 0) {
      event.sender.send('selected-file', result.filePaths[0]);
    }
  }).catch(err => {
    console.log(err);
  });
});

设置 preload.js

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

contextBridge.exposeInMainWorld('electronAPI', {
    selectFile: () => ipcRenderer.invoke('open-file-dialog'),
    handleFileSelected: (callback) => ipcRenderer.on('selected-file', callback)
});
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
    selectFile: () => ipcRenderer.invoke('open-file-dialog'),
    handleFileSelected: (callback) => ipcRenderer.on('selected-file', callback)
});

main.vue

js
<template>
  <div>
    <button @click="selectFile">选择文件</button>
    <div v-if="filePath">选择的文件路径: {{ filePath }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      filePath: ''
    };
  },
  methods: {
    async selectFile() {
      const path = await window.electronAPI.selectFile();
      if (path) {
        this.filePath = path;
        // 在这里调用你的通知方法,如 this.$notify(...)
      }
    }
  },
  created() {
    window.electronAPI.handleFileSelected((event, path) => {
      this.filePath = path;
      // 弹出通知
    });
  }
};
</script>
<template>
  <div>
    <button @click="selectFile">选择文件</button>
    <div v-if="filePath">选择的文件路径: {{ filePath }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      filePath: ''
    };
  },
  methods: {
    async selectFile() {
      const path = await window.electronAPI.selectFile();
      if (path) {
        this.filePath = path;
        // 在这里调用你的通知方法,如 this.$notify(...)
      }
    }
  },
  created() {
    window.electronAPI.handleFileSelected((event, path) => {
      this.filePath = path;
      // 弹出通知
    });
  }
};
</script>

可以看出用上面的双向通信,比较繁琐,还需要定义事件回调用来接收信息,不建议!

ipcRenderer.invokeipcRenderer.send 是 Electron 中用于进程间通信(IPC)的两种不同方法,它们在使用方式和用途上有所区别:

ipcRenderer.invoke

  • 用途ipcRenderer.invoke 用于异步发送一条消息到主进程,并期待一个响应。这个方法通常用于当渲染进程需要从主进程获得数据或结果时。
  • 返回值:这个方法返回一个 Promise,这个 Promise 解析的值是主进程通过 ipcMain.handle 注册的处理函数返回的值。
  • 例子
    javascript
    ipcRenderer.invoke('some-channel', arg1).then((result) => {
      // 处理结果
    });
    ipcRenderer.invoke('some-channel', arg1).then((result) => {
      // 处理结果
    });
  • 主进程中的处理:在主进程中,你需要使用 ipcMain.handle 来响应 invoke 请求:
    javascript
    ipcMain.handle('some-channel', async (event, arg1) => {
      // 返回处理结果
      return someResult;
    });
    ipcMain.handle('some-channel', async (event, arg1) => {
      // 返回处理结果
      return someResult;
    });

ipcRenderer.send

  • 用途ipcRenderer.send 用于异步发送一条消息到主进程,但不期待一个直接的响应。这个方法用于单向通信,例如通知主进程某件事情发生了,而不需要主进程回应。
  • 返回值:这个方法不返回值。如果需要响应,主进程必须通过 ipcRenderer.on 或者 ipcRenderer.once 另外发送一个消息。
  • 例子
    javascript
    ipcRenderer.send('some-channel', arg1);
    ipcRenderer.send('some-channel', arg1);
  • 主进程中的处理:在主进程中,你使用 ipcMain.on 来监听消息:
    javascript
    ipcMain.on('some-channel', (event, arg1) => {
      // 处理收到的消息
    });
    ipcMain.on('some-channel', (event, arg1) => {
      // 处理收到的消息
    });

推荐使用哪一个?

选择使用 invoke 还是 send 取决于你的具体需求:

  • 如果你需要一个请求-响应模式,比如从主进程请求数据并等待返回结果,那么 ipcRenderer.invokeipcMain.handle 是更合适的选择。它们提供了一种更清晰和更易于管理的方式来处理异步请求。

  • 如果你仅需要通知主进程某件事情发生,并且不需要主进程立即回应,ipcRenderer.sendipcMain.on 则更适合。这适用于不需要立即结果的单向通信场景。

在许多情况下,invoke/handle 提供了更简洁和直接的方式来处理需要响应的异步操作,因此在需要双向通信时通常更被推荐。然而,对于简单的事件通知或者不需要立即响应的情况,send/on 则足够且更简单。