electron入门使用

上一篇文章讲了electron 项目的创建和打包,本篇就简单讲讲它的使用

项目目录结构

src/main/index.ts

完整代码:

javascript 复制代码
import { app, shell, BrowserWindow } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'

function createWindow(): void {
  // createWindow() 函数将您的页面加载到新的 BrowserWindow 实例中
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    autoHideMenuBar: true,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false
    }
  })

  mainWindow.on('ready-to-show', () => {
    mainWindow.show()
  })

  mainWindow.webContents.setWindowOpenHandler((details) => {
    shell.openExternal(details.url)
    return { action: 'deny' }
  })

  //变量 ELECTRON_RENDERER_URL 是 Vite 开发服务运行的本地 URL
  if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
    //开发环境
    mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
  } else {
    //生产环境 文件路径
    mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
  }
}

//窗口应用的生命周期

//应用准备就绪时
app.whenReady().then(() => {
  electronApp.setAppUserModelId('com.electron')
  app.on('browser-window-created', (_, window) => {
    optimizer.watchWindowShortcuts(window)
  })
  //注意:窗口无法在 ready 事件前创建
  //加载新的窗口
  createWindow()

  app.on('activate', function () {
    //如果没有窗口打开则打开一个窗口 (macOS)
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

//关闭所有窗口时退出应用 (Windows & Linux)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

简单来讲:就是创建一个指定宽高的窗口mainWindow, 根据当前环境变量来确定窗口浏览器是加载本地html还是本地URL(mainWindow.loadURL/loadFile),然后在应用的生命周期函数中管理该窗口

如果是多页,可以这样使用

arduino 复制代码
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
  mainWindow.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/view.html`)
} else {
  mainWindow.loadFile(path.join(__dirname, '../renderer/view.html'))
}

主进程与渲染进程

主线程 :Electron程序会在运行package.json 文件的main 字段设置的入口文件,这个入口文件控制着Electron的主线程 在主进程中可以使用Node.js实例,负责应用的生命周期、展示原生窗口、执行特殊操作和管理渲染进程。

渲染器进程:负责展示图形内容,可以将渲染的网页指向web地址或本地HTML文件。渲染器和常规的网页行为很相似,访问的web API也相同。(本项目中react代码可以认为是渲染器进程)

两者关系图

一个主进程有多个渲染进程

一个 Electron 应用有且只有一个主进程。并且创建窗口等所有系统事件都要在主进程中进行。

主进程与渲染进程通信需要用到进程间通信模组IPC ,想要在渲染进程中使用Node.js等API需要使用预加载脚本安全地将特权API暴露至渲染进程。

预加载脚本

Electron的主进程是一个拥有着完全操作系统访问权限的Node.js环境,除了Electron模组之外也可以访问Node.js内置模块和所有通过npm安装的包。

出于安全原因,渲染进程默认跑在网页页面上而非Node.js中 。 为了将Electron的不同类型的进程桥接在一起,需要使用被称为预加载的特殊脚本

上篇文章使用electron-vite 工具构建的electron项目,已经为我们集中配置了主进程、渲染和预加载脚本Vite配置

preload/index.ts

javascript 复制代码
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'

// 自定义 APIs 给渲染进程
const api = {}

//使用' contextBridge ' api将Electron api暴露给渲染进程
// 默认开启了上下文隔离
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI)
    contextBridge.exposeInMainWorld('api', api)
  } catch (error) {
    console.error(error)
  }
} else {
  window.electron = electronAPI
  window.api = api
}

preload/index.d.ts

typescript 复制代码
import { ElectronAPI } from '@electron-toolkit/preload'

declare global {
  interface Window {
    electron: ElectronAPI
    api: unknown
  }
}

现在渲染进程中通过使用window.electron就可以访问到主线程中才有的API了,比如当前版本信息

进程间通信的几种方式

在 Electron 中,进程使用 ipcMainipcRenderer 模块,通过开发人员定义的"通道"传递消息来进行通信。 这些通道是 任意 可以随意命名它们 单向 双向

渲染进程 ➡️ 主线程 ipcMain.on ipcRenderer.send

例:渲染进程向主线程发消息,修改应用标题
main/index.ts

scss 复制代码
//应用准备就绪时
app.whenReady().then(() => {
  //主进程监听渲染进程发过来的消息
  ipcMain.on('set-title', (event, title) => {
    const webContents = event.sender
    const win = BrowserWindow.fromWebContents(webContents)
    win.setTitle(title)
  })

  //加载新的窗口
  createWindow()
  ...
})

preload/index.ts

javascript 复制代码
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'

// 自定义 APIs 给渲染进程
const api = {}

//使用' contextBridge ' api将Electron api暴露给渲染进程
// 默认开启了上下文隔离
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI)
    contextBridge.exposeInMainWorld('api', {
     //将单向 IPC 消息从渲染器进程发送到主进程
      setTitle: (val) => ipcRenderer.send('set-title', val)
    })
  } catch (error) {
    console.error(error)
  }
} else {
  window.electron = electronAPI
  window.api = api
}

preload/index.d.ts

需要在此文件中声明setTitle方法,不然会报ts问题,下面例代码就不做解释了

typescript 复制代码
declare global {
  interface Window {
    electron: ElectronAPI
    api: {
      setTitle: (value: string) => void
    }
  }
}

渲染页面renderer/src/components

css 复制代码
<button onClick={(): void => window.api.setTitle('嘿 你真帅')}>更新标题</button>

效果

渲染进程 ↔️ 主线程 ipcMain.handle ipcRenderer.invoke

渲染器进程代码调用主进程模块并等待结果

例:渲染进程需要获取当前硬件的MAC地址
main/index.ts

javascript 复制代码
import os from 'os'

function handleNetwork(): string {
  let mac = ''
  const networkInterfaces = os.networkInterfaces()
  for (const i in networkInterfaces) {
    for (const j in networkInterfaces[i]) {
      if (
        networkInterfaces[i][j]['family'] === 'IPv4' &&
        networkInterfaces[i][j]['mac'] !== '00:00:00:00:00:00' &&
        networkInterfaces[i][j]['address'] !== '127.0.0.1'
      ) {
        mac = networkInterfaces[i][j]['mac']
      }
    }
  }
  return mac
}

app.whenReady().then(() => {
  //双向通信
  ipcMain.handle('get-network', handleNetwork)
})

preload/index.ts

arduino 复制代码
contextBridge.exposeInMainWorld('api', {
      //暴露了一个`get-network` 函数,它调用并返回
      getNetwork: () => ipcRenderer.invoke('get-network')
    })

渲染页面renderer/src/components

javascript 复制代码
window.api.getNetwork().then((value) => console.log('value', value)) //value 9c:3e:53:93:55:11

渲染进程 ⬅️ 主进程 WebContents

将消息从主进程发送到渲染器进程时,需要指定是哪一个渲染器接收消息 例:主进程在接到某消息后要将该消息发送给渲染进程
main/index.ts

javascript 复制代码
 //主进程发送给mainWindow这个渲染进程,修改背景颜色
 //mainWindow(窗口名)
  setTimeout(() => {
    mainWindow.webContents.send('change-color', 'pink')
  }, 2000)

preload/index.ts

javascript 复制代码
contextBridge.exposeInMainWorld('api', {
      //监听
      onChangeColor: (callback) => ipcRenderer.on('change-color', (e, value) => callback(value))
    })

渲染页面renderer/src/components

scss 复制代码
  useEffect(() => {
    //监听主进程发送的消息
    window.api.onChangeColor((value: string) => {
      setColor(value)
    })
  }, [])

渲染进程 ➡️ 渲染进程

1、将主进程作为渲染器之间的消息代理

2、使用MessagePort

✨✨✨ 如有不正确的地方,欢迎指出哈~

相关推荐
MickeyCV16 分钟前
Nginx学习笔记:常用命令&端口占用报错解决&Nginx核心配置文件解读
前端·nginx
祈澈菇凉33 分钟前
webpack和grunt以及gulp有什么不同?
前端·webpack·gulp
zy01010140 分钟前
HTML列表,表格和表单
前端·html
初辰ge43 分钟前
【p-camera-h5】 一款开箱即用的H5相机插件,支持拍照、录像、动态水印与样式高度定制化。
前端·相机
HugeYLH1 小时前
解决npm问题:错误的代理设置
前端·npm·node.js
六个点2 小时前
DNS与获取页面白屏时间
前端·面试·dns
道不尽世间的沧桑2 小时前
第9篇:插槽(Slots)的使用
前端·javascript·vue.js
bin91532 小时前
DeepSeek 助力 Vue 开发:打造丝滑的滑块(Slider)
前端·javascript·vue.js·前端框架·ecmascript·deepseek
uhakadotcom2 小时前
最新发布的Tailwind CSS v4.0提供了什么新能力?
前端
GISer_Jing2 小时前
Node.js中如何修改全局变量的几种方式
前端·javascript·node.js