用 Electron-react 实现我的第一个客户端

摸鱼无聊怎么办,怼着代码死里干!

为了接触更多的新技术,拓展自己的技术能力,本文给大家带来我第一次使用electron的技术文章,记录了对electron的一些基本的认识,主要介绍了electron的一些基本的架构和api的使用,由于对react的熟悉度还是比较低,所以这次选择的脚手架是electron-react

快速安装

先简单引入一段 electron-react-boilerplate 的安装模版

js 复制代码
# Clone the boilerplate:  
git clone --depth=1 \  
https://github.com/electron-react-boilerplate/electron-react-boilerplate \  
your-project-name  
  
cd your-project-name  
  
# Install dependencies:  
npm install

先看最终运行效果

安装问题

将项目克隆到本地后会出现无法安装的报错

解决方案

  1. 清理 npm 缓存: 尝试清理 npm 的缓存,运行以下命令来清理 npm 缓存:
js 复制代码
npm cache clean --force
  1. 删除 node_modulespackage-lock.json: 有时候,node_modulespackage-lock.json 中的一些缓存或冲突可能导致问题。需要尝试删除它们并重新安装依赖:
js 复制代码
rmdir /s /q node_modules 
del package-lock.json 
npm install
  1. 重新运行即可解决问题

electron结构

我们先简单了解一下 electron 主要分为主进程和渲染进程 为了下面的代码解析做一个基础

主进程

"main": "./src/main/main.ts" 是在 package.json 文件中指定 Electron 应用程序主进程(Main Process)入口文件的配置。

渲染进程

在主窗口载入渲染进程页面

js 复制代码
//main
  mainWindow.loadURL(resolveHtmlPath('index.html'));
js 复制代码
export function resolveHtmlPath(htmlFileName: string) {
  if (process.env.NODE_ENV === 'development') {
    const port = process.env.PORT || 1212;
    const url = new URL(`http://localhost:${port}`);
    url.pathname = htmlFileName;
    return url.href;
  }
  return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`;
}

通过该方式将渲染进程加载到窗口中

主进程解读

1.导入依赖

首先导入了一些 Electron 相关的模块,包括pathappBrowserWindowshellipcMain等。还导入了其他模块,如electron-updater用于自动更新应用、electron-log用于记录日志等。

js 复制代码
import path from 'path';
import { app, BrowserWindow, shell, ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import MenuBuilder from './menu';
import { resolveHtmlPath } from './util';

主进程和渲染进程通信

上面导入的ipcMain主要是用于主进程和渲染进程之间的通信

主进程监听了名为 'ipc-example' 的 IPC 事件。当渲染进程发送 'ipc-example' 事件时,主进程将打印接收到的消息,并回复 'ipc-example' 事件,回复内容为 'IPC test: pong'。

js 复制代码
//main
ipcMain.on('ipc-example', async (event, arg) => {
  const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`;
console.log(msgTemplate(arg),'收到渲染进程信息');
  event.reply('ipc-example', msgTemplate('pong'));
});
js 复制代码
/renderer
// calling IPC exposed from preload script
window.electron.ipcRenderer.once('ipc-example', (arg) => {
  // eslint-disable-next-line no-console
    console.log(arg,'收到主进程信息');
});

渲染进程的打印在启动窗口中打印

主进程在vscode控制台打印,我们可以看到已经接收到渲染进程发送的信息,但是结果是乱码

解决控制台乱码 :

启动的命令中加上 chcp 65001

js 复制代码
"start": "chcp 65001 && ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer",

开发环境配置

代码中检查了环境变量 NODE_ENVDEBUG_PROD,以确定是否为开发环境。如果是开发环境,会调用 electron-debug 模块来支持 Electron 的开发调试。

js 复制代码
const isDebug =
  process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';

if (isDebug) {
  require('electron-debug')();
}

安装开发者工具扩展: 在开发环境下,通过 electron-devtools-installer 模块来安装开发者工具扩展,其中包括 REACT_DEVELOPER_TOOLS

js 复制代码
const installExtensions = async () => {
  const installer = require('electron-devtools-installer');
  const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
  const extensions = ['REACT_DEVELOPER_TOOLS'];

  return installer
    .default(
      extensions.map((name) => installer[name]),
      forceDownload
    )
    .catch(console.log);
};

创建主窗口

createWindow 函数用于创建 Electron 主窗口。这里定义了主窗口的属性,包括尺寸、图标、preload 脚本等,并加载 index.html 页面。在主窗口准备好显示后,会显示主窗口,也可以选择最小化窗口。

  1. isDebugisDebug 是一个布尔值,表示是否处于调试模式。如果是调试模式,则会执行 installExtensions 函数,安装开发工具扩展。
  2. RESOURCES_PATHgetAssetPath:这两个变量用于确定资源文件的路径。RESOURCES_PATH 是一个字符串,表示资源文件夹的路径。getAssetPath 是一个函数,接受一个或多个路径片段,然后返回它们在资源文件夹中的完整路径。
  3. mainWindowmainWindow 是一个 Electron BrowserWindow 对象,用于创建和管理应用程序的主窗口。在这个函数中,它会被创建并设置一些属性,如窗口大小、图标、预加载脚本等。
  4. mainWindow.loadURL:该方法用于加载应用程序的主页面,它会将指定的 HTML 文件加载到 mainWindow 窗口中。
  5. mainWindow.on('ready-to-show'):该事件监听器会在 mainWindow 窗口加载完毕并准备好显示时触发。在这里,它会检查 mainWindow 是否为 null,并根据 process.env.START_MINIMIZED 的值决定是最小化窗口还是显示窗口。
  6. mainWindow.on('closed'):该事件监听器会在 mainWindow 窗口关闭时触发,用于清理资源并将 mainWindow 设置为 null。
  7. menuBuilder.buildMenu():这是一个用于创建应用程序菜单的函数,将在 mainWindow 中创建自定义菜单。
  8. mainWindow.webContents.setWindowOpenHandler():这是一个用于处理应用程序中链接的函数。它会在用户点击链接时打开默认浏览器,而不是在 Electron 中打开新窗口。
js 复制代码
const createWindow = async () => {
  if (isDebug) {
    await installExtensions();
  }

  const RESOURCES_PATH = app.isPackaged
    ? path.join(process.resourcesPath, 'assets')
    : path.join(__dirname, '../../assets');

  const getAssetPath = (...paths: string[]): string => {
    return path.join(RESOURCES_PATH, ...paths);
  };

  mainWindow = new BrowserWindow({
    show: false,
    width: 1024,
    height: 728,
    icon: getAssetPath('icon.png'),
    webPreferences: {
      preload: app.isPackaged
        ? path.join(__dirname, 'preload.js')
        : path.join(__dirname, '../../.erb/dll/preload.js'),
    },
  });

  mainWindow.loadURL(resolveHtmlPath('index.html'));

  mainWindow.on('ready-to-show', () => {
    if (!mainWindow) {
      throw new Error('"mainWindow" is not defined');
    }
    if (process.env.START_MINIMIZED) {
      mainWindow.minimize();
    } else {
      mainWindow.show();
    }
  });

  mainWindow.on('closed', () => {
    mainWindow = null;
  });

  const menuBuilder = new MenuBuilder(mainWindow);
  menuBuilder.buildMenu();

  // Open urls in the user's browser
  mainWindow.webContents.setWindowOpenHandler((edata) => {
    shell.openExternal(edata.url);
    return { action: 'deny' };
  });

  // Remove this if your app does not use auto updates
  // eslint-disable-next-line
  new AppUpdater();
};
  1. 应用事件监听: 对应用的一些事件进行监听,如 window-all-closed 事件,当所有窗口关闭时,退出应用(在 macOS 中会保持应用在内存中)。还有 app.whenReady() 事件,在应用准备好后创建主窗口,并在 macOS 中点击 dock 图标时重新创建窗口。
  2. 自动更新: 代码中调用了 electron-updaterautoUpdater 来进行自动更新检查。自动更新功能需要结合应用更新服务器,当有新版本可用时,应用会自动下载并应用更新。

自动更新

自动更新: 代码中调用了 electron-updaterautoUpdater 来进行自动更新检查。自动更新功能需要结合应用更新服务器,当有新版本可用时,应用会自动下载并应用更新。

js 复制代码
class AppUpdater {
  constructor() {
    // log.transports.file.level = 'info';
    log.transports.console.format = '{h}:{i}:{s} {text}';
    log.transports.console.level = 'debug'; // 设置日志级别为 debug 或其他级别
    autoUpdater.logger = log;
    autoUpdater.checkForUpdatesAndNotify();
  }
}

🙏 感谢您花时间阅读这篇文章!如果觉得有趣或有收获,请关注我的更新,给个喜欢和分享。您的支持是我写作的最大动力!✍️🌟

往期好文推荐

相关推荐
光影少年8 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_10 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891112 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾13 分钟前
前端基础-html-注册界面
前端·算法·html
Dragon Wu15 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym20 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫21 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫25 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat26 分钟前
前端性能优化2
前端
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js