electron基本教程

1. 应用基础

入口main.js,在app中启动一个window加载preload.js和html文件,然后html中添加render.js用于渲染。

定义preload.js是用于渲染页面和主程序进行交互,使用contextBridge将nodejs的api暴露给渲染页面),主要代码如下:

复制代码
app.whenReady().then(() =>{
	const mainWindow = new BrowserWindow({webPreferences: {preload: path.join(__dirname, 'preload.js')}})
	mainWindow.loadFile('index.html')
	}

BrowserWindow中可以继续用BrowserView来加载外部网址:

复制代码
const view = new BrowserView()
win.setBrowserView(view)
view.webContents.loadURL('https://electronjs.org')

2. main与render的交互

搞这么复杂的原因就是浏览器的js页面不允许加载js库,很多复杂的功能实现不了,必须得在main.js中实现。

需要preload.js的原因是,渲染进程中不允许直接使用ipcRender;

需要渲染js的原因是:preload.js无法操纵页面元素

真是麻了。总结一下:

  1. html中使用<script src="render.js"></script>加载页面事件监听和元素操纵的代码
  2. preload.js使用contextBridge.exposeInMainWorld暴露接口给render.js,在render.js中使用window.接口进行调用
  3. main.js相当于后端代码,除了初始化app、创建window等外,还可以创建一系列函数,提供给preload.js进行使用。

使用ipcMain & ipcRender。ipcRenderer提供方法从渲染进程 (web 页面) 发送同步或异步的消息到主进程。ipcMain则在主进程接受消息并进行处理,也可以将结果返回给渲染进程。

示例用的交互代码如下:

复制代码
## preload.js
window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }

  for (const type of ['chrome', 'node', 'electron']) {
    replaceText(`${type}-version`, process.versions[type])
  }
})

渲染页面通过如下代码加载渲染用的js代码:

复制代码
<script src="./renderer.js"></script>

官方的electron fiddle示例跑出来也有问题,这里总结一下能成功运行的方法。

2.1 同步:使用ipcRender.sendSync和ipcMain.on(event.returnValue)

示例如下:

复制代码
// render.js
const { ipcRenderer } = require('electron');
function sendSyncMessageToMain() {
  const replyMessage = ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程通过 syncSend 发送给主进程的消息');
  console.log('replyMessage', replyMessage); // '主进程回复的消息'
}

// main.js
const { ipcMain } = require('electron');
ipcMain.on('render-send-sync-to-main', (event, message) => {
  console.log(`receive message from render: ${message}`)
  event.returnValue = '主进程回复的消息';
})

2.2 异步:使用ipcRender.invoke和ipcMain.handle(return)

渲染进程异步等待主进程的回应, invoke 的返回值是一个 Promise 。我们可以不使用 await 来接收 invoke 的返回结果,打印的结果符合我们的预期,是一个 Promise。

复制代码
// render.js
const { ipcRenderer } = require('electron');
async function sendAsyncMessageToMain() {
  const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息');
  console.log('replyMessage', replyMessage);
}]

// main.js
const { ipcMain } = require('electron');
ipcMain.handle('render-invoke-to-main', async (event, message) => {
  console.log(`receive message from render: ${message}`)
  const result = await asyncWork();
  return result;
})

3. 综合示例

下面是使用highs.js进行线性规划求解的一个例子,效果如图:

3.1 后台函数main.js

定义求解函数

复制代码
const highs_promise = require("highs")();
const { app,BrowserWindow, ipcMain } = require('electron');
const path = require('node:path')
let win;
function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            nodeIntegration: true,
            preload: path.join(__dirname, 'preload.js')
        }
    });
    win.loadFile('index.html');
    win.on('closed', () => {win = null;});
}

app.whenReady().then(() => {
    createWindow();
    ipcMain.handle('solve-lp', async (event, data) => {   
        const highs = await highs_promise
        return highs.solve(data)
        })
})

3.2 接口封装preload.js

复制代码
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
  solve: async (data) => {return await ipcRenderer.invoke('solve-lp', data)}
})

3.3 前端函数render.js

添加按钮事件,将求解结果绑定到dom元素上

复制代码
document.getElementById('solve-btn').addEventListener('click', (event) => {
    event.preventDefault()
    const promise = window.electronAPI.solve(document.getElementById('Problem').value)
    promise.then(res => {document.getElementById('result').innerHTML = JSON.stringify(res, null, "  ")})
})

3.4 网页文件index.html

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Linear Programming Solver</title>
</head>
<body>
    <h1>线性规划求解器</h1>
    <h2>输入问题</h2>
    <form id="linear-form">
        <textarea id="Problem" name="Problem" rows="10" cols="80">
Maximize obj:x1 + 2 x2 + 4 x3 + x4
Subject To
c1: - x1 + x2 + x3 + 10 x4 <= 20
c2: x1 - 4 x2 + x3 <= 30
c3: x2 - 0.5 x4 = 0
Bounds
0 <= x1 <= 40
2 <= x4 <= 3
End</textarea><br><br>
        <button id="solve-btn">求解</button>
    </form>
    <div id="result"></div>
    <script src="linear-form.js"></script>
</body>
</html>

3.5 项目配置文件package.json

复制代码
{
  "name": "test",
  "version": "1.0.0",
  "description": "test",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "test",
  "license": "ISC",
  "dependencies": {"highs": "^1.8.0"}
}

使用cnpm install highs --save安装好highs,然后执行cnpm start就可以运行程序啦。

4. 高级功能

autoUpdater

复制代码
const { app, autoUpdater } = require('electron/main')
app.whenReady().then(() => {
  const server = 'https://your-deployment-url.com'
  const feed = `${server}/update/${process.platform}/${app.getVersion()}`
  try {
    autoUpdater.setFeedURL(feed)
  } catch (error) {
    console.log(error)
  }
})

clipboard

main.js中的函数如下:

复制代码
const { app, BrowserWindow, ipcMain, clipboard } = require('electron/main')
ipcMain.handle('clipboard:readText', () => {
  return clipboard.readText()
})
ipcMain.handle('clipboard:writeText', (event, text) => {
  clipboard.writeText(text)
})

通过preload.js提供给html文件:

复制代码
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('clipboard', {
  readText: () => ipcRenderer.invoke('clipboard:readText'),
  writeText: (text) => ipcRenderer.invoke('clipboard:writeText', text)
})

然后在render.js中定义html的页面渲染函数:

复制代码
window.addEventListener('DOMContentLoaded', () => {
  const copyButton = document.querySelector('#copy')
  const pasteButton = document.querySelector('#paste')
  const textarea = document.querySelector('textarea')
  copyButton.onclick = () => {
    window.clipboard.writeText('Hello from Electron!') # 向剪贴板中写入text
  }
  pasteButton.onclick = async () => {
    textarea.value = await window.clipboard.readText()
  }
})

文件对话框

复制代码
const { filePaths, canceled } = await dialog.showOpenDialog(mainWindow, {properties: ['openFile']})

菜单栏

复制代码
  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)

网络

复制代码
const { app, net } = require('electron/main')

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()
})
相关推荐
逆袭的小黄鸭几秒前
深入剖析 JavaScript 执行上下文:代码运行的幕后机制
前端·javascript·面试
小old弟几秒前
闭包:从入门到“顿悟”的奇幻之旅
前端
晴殇i1 分钟前
抛弃 JavaScript 立即执行函数,这个方案更简洁更优雅
前端·javascript
码是生活2 分钟前
鸿蒙开发排坑:解决 resourceManager.getRawFileContent() 获取文件内容为空问题
前端·harmonyos
zoahxmy09293 分钟前
Vue3 视频播放与截图功能实现
javascript·vue.js
婷婷婷婷5 分钟前
v-copyText 自定义指令 —— 复制文本内容
前端
waylon111137 分钟前
【HOC】高阶组件在Vue老项目中的实战应用 - 模块任意排序
前端·vue.js·面试
阳阳羊8 分钟前
Mpx 动画
前端
编程社区管理员9 分钟前
「2025最新版React+Ant Design+Router+TailwindCss全栈攻略:从零到实战,打造高颜值企业级应用
前端·react.js·前端框架
DJA_CR9 分钟前
解决在 TSX 中使用 `RouterView` + `KeepAlive` 不生效问题
前端·vue.js