德胜云资讯,添加一些关于程序相关的内容,仅供大家学习交流(https://www.wxclwl.com)

网站地图

搜索
德胜云咨询
前端分类 javascript CSS 正则表达式 html 前端框架 typescript Ajax
热门标签:
最新标签:

react+typescript高仿Electron结合React和TypeScript进行开发react配置typescript深度揭秘,

日期:2023/03/26 15:29作者:金佳蓉人气:

导读:我们可以使用一套代码打包成Mac、Windows和Linux的应用,electron比你想象的更简单,如果把你可以建一个网站,你就可以建一个桌面...

结合React+TypeScript进行Electron开发

1. electron基本简介

electron是使用JavaScript,HTML和CSS构建跨平台桌面应用程序。我们可以使用一套代码打包成Mac、Windows和Linux的应用,electron比你想象的更简单,如果把你可以建一个网站,你就可以建一个桌面应用程序,我们只需要把精力放在应用的核心上即可。

为什么选择electron?

Electron 可以让你使用纯JavaScript调用丰富的原生APIs来创造桌面应用。你可以把它看作是专注于桌面应用。在PC端桌面应用开发中,nwjs和electron都是可选的方案,它们都是基于Chromium和Node的结合体,而electron相对而言是更好的选择方案,它的社区相对比较活跃,bug比较少,文档相对利索简洁。electron相对来说比nw.js靠谱,有一堆成功的案例:Atom编辑器 Visual Studio Code WordPress等等。Node.js的所有内置模块都在Electron中可用。

2. 快速上手

2.1 安装React(template为ts)

yarn create react-app electron-demo-ts --template typescript

2.2 快速配置React

工程架构

index.html

<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Bleaks electron app base react" /> <meta http-equiv="Content-Security-Policy" content="default-src self unsafe-inline;"> <link rel="stylesheet" href="%PUBLIC_URL%/css/reset.css"> <title>electron App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body></html>

App.tsx

import React from react export default function App() { return ( <div>App</div> )}

index.tsx

import React from react;import ReactDOM from react-dom/client;import App from ./App;import reportWebVitals from ./reportWebVitals; const root = ReactDOM.createRoot( document.getElementById(root) as HTMLElement);root.render( // <React.StrictMode> <App /> // </React.StrictMode>); // If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

2.3 安装electron

electron包安装到您的应用程序的devDependencies.

// npmnpm install --save-dev electron// yarnyarn add --dev electron

2.4 配置main.js、preload.js和package.json文件

main.js

// 导入app、BrowserWindow模块// app 控制应用程序的事件生命周期。事件调用app.on(eventName, callback),方法调用app.functionName(arg)// BrowserWindow 创建和控制浏览器窗口。new BrowserWindow([options]) 事件和方法调用同app// Electron参考文档 https://www.electronjs.org/docsconst {app, BrowserWindow, nativeImage } = require(electron)const path = require(path)// const url = require(url); function createWindow () { // Create the browser window. const mainWindow = new BrowserWindow({ width: 800, // 窗口宽度 height: 600, // 窗口高度 // title: "Electron app", // 窗口标题,如果由loadURL()加载的HTML文件中含有标签<title>,该属性可忽略 icon: nativeImage.createFromPath(public/favicon.ico), // "string" || nativeImage.createFromPath(public/favicon.ico)从位于 path 的文件创建新的 NativeImage 实例 webPreferences: { // 网页功能设置 webviewTag: true, // 是否使用<webview>标签 在一个独立的 frame 和进程里显示外部 web 内容 webSecurity: false, // 禁用同源策略 preload: path.join(__dirname, preload.js), nodeIntegration: true // 是否启用node集成 渲染进程的内容有访问node的能力,建议设置为true, 否则在render页面会提示node找不到的错误 } }) // 加载应用 --打包react应用后,__dirname为当前文件路径 // mainWindow.loadURL(url.format({ // pathname: path.join(__dirname, ./build/index.html), // protocol: file:, // slashes: true // })); // 因为我们是加载的react生成的页面,并不是静态页面 // 所以loadFile换成loadURL。 // 加载应用 --开发阶段 需要运行 yarn start mainWindow.loadURL(http://localhost:3000); // 解决应用启动白屏问题 mainWindow.on(ready-to-show, () => { mainWindow.show(); mainWindow.focus(); }); // 当窗口关闭时发出。在你收到这个事件后,你应该删除对窗口的引用,并避免再使用它。 mainWindow.on(closed, () => { mainWindow = null; }); // 在启动的时候打开DevTools mainWindow.webContents.openDevTools()} app.allowRendererProcessReuse =true; // This method will be called when Electron has finished// initialization and is ready to create browser windows.// Some APIs can only be used after this event occurs.app.whenReady().then(() =>{ console.log(qpp---whenready); createWindow();}) // Quit when all windows are closed.app.on(window-all-closed, function () { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q console.log(window-all-closed); if (process.platform !== darwin) app.quit()}) app.on(activate, function () { // On macOS its common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) createWindow()}) // In this file you can include the rest of your apps specific main process// code. You can also put them in separate files and require them here.

package.json

这时候我们来修改package.json文件。

配置启动文件,添加main字段,我们这里也就是main.js文件。如果没有添加,Electron 将尝试加载包含在package.json文件目录中的index.js文件。配置运行命令,使用"electron": "electron ." 区别于react的启动命令"start": "react-scripts start",安装concurrently: yarn add concurrently{ ... "main": "main.js", // 配置启动文件 "homepage": ".", // 设置应用打包的根路径 "scripts": { "start": "react-scripts start", // react 启动命令 "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "electron": "electron .", // electron 启动命令 "dev": "concurrently \"npm run start\" \"npm run electron\"" },}

preload.js

window.addEventListener(DOMContentLoaded, () => { const replaceText = (selector, text) => { const element = document.getElementById(selector) if (element) element.innerText = text } for (const dependency of [chrome, node, electron]) { replaceText(`${dependency}-version`, process.versions[dependency]) }})

此时的工程架构

2.5 运行electron项目

yarn start 然后再开一个终端yarn electron或者是npm run dev

其实我们就可以看出Electron就是一个应用套了一个谷歌浏览器壳子,然后里面是前端页面。

2.6 打包项目

使用electron-packager依赖:

yarn add --dev electron-packager

package.json配置打包命令:

"package": "electron-packager . bleak-electron-app --platform=win32 --arch=x64 --overwrite --electron-version=18.1.0 --icon=./public/favicon.ico"

配置解释:

electron-packager <应用目录> <应用名称> <打包平台> <架构x86 还是 x64> <架构> <electron版本> <图标>overwrite 如果输出目录已经存在,替换它

然后运行命令:

yarn package

打包时间慢的话可按照下面两种方式优化:

方法1:在执行electron-packager前先运行set ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/方法2:在electron-packager命令行加入参数--download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/(Windows x64)完整版如下:electron-packager . bleak-electron-app --platform=win32 --arch=x64 --overwrite --electron-version=18.0.4 --download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/

然后运行bleak-electron-app-win32-x64里面的exe文件就可以了。

3. 自动刷新页面

当你用react开发的时候,网页内容会自动热更新,但是electron窗口的main.js中代码发生变化时不能热加载。

安装插件electron-reloader:

yarn add --dev electron-reloadernpm install --save-develectron-reloader

然后在路口引用插件:

const reloader = require(electron-reloader)reloader(module)

就可以实现electron插件热更新。

4. 主进程和渲染进程

Electron运行package.json的main脚本的进程称为主进程。在主进程中运行的脚本通过创建web页面来展示用户节面,一个Electron应用总是有且只有一个主进程。

由于Electron使用了Chromium来展示web页面,所以Chromium的多进程架构也被使用到,每个Electron钟大哥web页面运行在它的叫渲染进程的进程中。

在普通的浏览器中,web页面无法访问操作系统的原生资源。然而Electron的用户在Node.js的API支持下可以在页面中和操作系统进行一些底层交互。

ctrl + shift + i 打开渲染进程调试(devtools)

默认打开调试:

// 在启动的时候打开DevToolsmainWindow.webContents.openDevTools()

5.定义原生菜单、顶部菜单

5.1 自定义菜单

可以使用Menu菜单来创建原生应用菜单和上下文菜单。

首先判断是什么平台,是mac还是其他:const isMac = process.platform === darwin创建菜单模板:

其是由一个个MenuItem组成的,可以在菜单项官网API查看。

const template = [ // { role: appMenu } // 如果是mac系统才有 ...(isMac ? [{ label: app.name, submenu: [ { role: about }, { type: separator }, { role: services }, { type: separator }, { role: hide }, { role: hideOthers }, { role: unhide }, { type: separator }, { role: quit } ] }] : []), // { role: fileMenu } { label: 文件, submenu: [ isMac ? { role: close } : { role: quit, label: 退出 } ] }, // { role: editMenu } { label: 编辑, submenu: [ { role: undo, label: 撤消 }, { role: redo, label: 恢复 }, { type: separator }, { role: cut, label: 剪切 }, { role: copy, label: 复制 }, { role: paste, label: 粘贴 }, ...(isMac ? [ { role: pasteAndMatchStyle }, { role: delete }, { role: selectAll }, { type: separator }, { label: Speech, submenu: [ { role: startSpeaking }, { role: stopSpeaking } ] } ] : [ { role: delete, label: 删除 }, { type: separator }, { role: selectAll, label: 全选 } ]) ] }, // { role: viewMenu } { label: 查看, submenu: [ { role: reload, label: 重新加载 }, { role: forceReload, label: 强制重新加载 }, { role: toggleDevTools, label: 切换开发工具栏 }, { type: separator }, { role: resetZoom, label: 原始开发工具栏窗口大小 }, { role: zoomIn, label: 放大开发工具栏窗口}, { role: zoomOut, label: 缩小开发工具栏窗口 }, { type: separator }, { role: togglefullscreen, label:切换开发工具栏全屏 } ] }, // { role: windowMenu } { label: 窗口, submenu: [ { role: minimize, label:最小化 }, ...(isMac ? [ { type: separator }, { role: front }, { type: separator }, { role: window } ] : [ { role: close, label: 关闭 } ]) ] }, { role: help, label: 帮助, submenu: [ { label: 从Electron官网学习更多, click: async () => { const { shell } = require(electron) await shell.openExternal(https://electronjs.org) } } ] }]根据模板创建menu:const menu = Menu.buildFromTemplate(template)设置菜单:Menu.setApplicationMenu(menu)

5.2 给菜单定义点击事件

可以通过click属性来设置点击事件

5.3 抽离菜单定义

创建一个menu.js:

const {app, Menu } = require(electron) const isMac = process.platform === darwin const template = [ // { role: appMenu } // 如果是mac系统才有 ...(isMac ? [{ label: app.name, submenu: [ { role: about }, { type: separator }, { role: services }, { type: separator }, { role: hide }, { role: hideOthers }, { role: unhide }, { type: separator }, { role: quit } ] }] : []), // { role: fileMenu } { label: 文件, submenu: [ isMac ? { role: close } : { role: quit, label: 退出 } ] }, // { role: editMenu } { label: 编辑, submenu: [ { role: undo, label: 撤消 }, { role: redo, label: 恢复 }, { type: separator }, { role: cut, label: 剪切 }, { role: copy, label: 复制 }, { role: paste, label: 粘贴 }, ...(isMac ? [ { role: pasteAndMatchStyle }, { role: delete }, { role: selectAll }, { type: separator }, { label: Speech, submenu: [ { role: startSpeaking }, { role: stopSpeaking } ] } ] : [ { role: delete, label: 删除 }, { type: separator }, { role: selectAll, label: 全选 } ]) ] }, // { role: viewMenu } { label: 查看, submenu: [ { role: reload, label: 重新加载 }, { role: forceReload, label: 强制重新加载 }, { role: toggleDevTools, label: 切换开发工具栏 }, { type: separator }, { role: resetZoom, label: 原始开发工具栏窗口大小 }, { role: zoomIn, label: 放大开发工具栏窗口}, { role: zoomOut, label: 缩小开发工具栏窗口 }, { type: separator }, { role: togglefullscreen, label:切换开发工具栏全屏 } ] }, // { role: windowMenu } { label: 窗口, submenu: [ { role: minimize, label:最小化 }, ...(isMac ? [ { type: separator }, { role: front }, { type: separator }, { role: window } ] : [ { role: close, label: 关闭 } ]) ] }, { role: help, label: 帮助, submenu: [ { label: 从Electron官网学习更多, click: async () => { const { shell } = require(electron) await shell.openExternal(https://electronjs.org) } } ] }] const menu = Menu.buildFromTemplate(template)Menu.setApplicationMenu(menu)

然后在main.jscreateWindow的方法使用require调用:

const createWindow = () => { ...... require(./menu) ......}

5.4 自定义顶部菜单

我们可以自定义顶部菜单,通过以下两个步骤进行:

先通过frame创建无边框窗口。function createWindow () { const mainWindow = new BrowserWindow({ ...... frame: false }) }然后再通过前端页面布局设置顶部菜单

如果想让顶部菜单支持拖拽,可以加如下css:

-webkit-app-region: drag;

5.5 在渲染进程中使用主进程方法remote和electron(点击创建新窗口)

我们想要通过remote来使用主进程方法和功能。

首先要安装@electron/remoteyarn add @electron/remote在主进程main.js中配置remote:const remote = require("@electron/remote/main")remote.initialize() const createWindow = () => { let mainWindow = new BrowserWindow({ ...... webPreferences: { // 网页功能设置 ...... nodeIntegration: true, // 是否启用node集成 渲染进程的内容有访问node的能力,建议设置为true, 否则在render页面会提示node找不到的错误 contextIsolation : false, //允许渲染进程使用Nodejs } }) remote.enable(mainWindow.webContents)}在渲染进程中使用remote的BrowserWindow:App.tsximport React from react// 使用electron的功能// const electron = window.require(electron)// 使用remoteconst { BrowserWindow } = window.require("@electron/remote") export default function App() { const openNewWindow = () => { new BrowserWindow({ width:500, height:500 }) } return ( <div> App <div> <button onClick={openNewWindow}>点我开启新窗口</button> </div> </div> )}

我们想要通过使用electron提供给渲染进程的API:

const electron = window.require(electron)

然后从electron中提取方法。

5.6 点击打开浏览器

使用electron中的shell可以实现此功能:

import React from react// 使用electron的功能// const electron = window.require(electron)// 使用remote// const { BrowserWindow } = window.require("@electron/remote")// 使用shellconst { shell } = window.require(electron) export default function App() { const openNewWindow = () => { shell.openExternal(https://www.baidu.com) } return ( <div> App <div> <button onClick={openNewWindow}>点我开启新窗口打开百度</button> </div> </div> )}

6. 打开对话框读取文件

6.1 读取文件

主进程中的dialog模块可以显示用于打开和保存文件、警报等的本机系统对话框。

因为dialog模块属于主进程,如果我们在渲染进程中需要使用则需要使用remote模块。

App.tsx

import React,{ useRef } from react// 使用electron的功能// const electron = window.require(electron) // 使用remote// const remote = window.require(@electron/remote)// const { BrowserWindow } = window.require("@electron/remote")const { dialog } = window.require("@electron/remote") // 使用shellconst { shell } = window.require(electron) // 使用fsconst fs = window.require(fs) export default function App() { // ref const textRef = useRef<HTMLTextAreaElement | null>(null) const openNewWindow = () => { shell.openExternal(https://www.baidu.com) } const openFile = () => { const res = dialog.showOpenDialogSync({ title: 读取文件, // 对话框窗口的标题 buttonLabel: "读取", // 按钮的自定义标签, 当为空时, 将使用默认标签。 filters: [ // 用于规定用户可见或可选的特定类型范围 //{ name: Images, extensions: [jpg, png, gif, jpeg, webp] }, //{ name: Movies, extensions: [mkv, avi, mp4] }, { name: Custom File Type, extensions: [js] }, { name: All Files, extensions: [*] }, ] }) const fileContent:string = fs.readFileSync(res[0]).toString(); (textRef.current as HTMLTextAreaElement).value = fileContent } return ( <div> App Test <div> <button onClick={openNewWindow}>点我开启新窗口打开百度</button> </div> <div> <button onClick={openFile}>打开文件</button> <textarea ref={textRef}></textarea> </div> </div> )}

6.2 保存文件

保存文件需要使用dialog函数里的showSaveDialogSync,与之前的读取文件所用到的showOpenDialogSync类似:

const saveFile = () => { const res = dialog.showSaveDialogSync({ title:保存文件, buttonLable: "保存", filters: [ { name: index, extensions: [js]} ] }) fs.writeFileSync(res, textRef.current?.value)}

7. 定义快捷键

7.1 主线程定义

引入globalShortcut

const {app, BrowserWindow, nativeImage, globalShortcut } = require(electron)

注册快捷键打印字符串、窗口最大化、窗口最小化、关闭窗口。

const createWindow = () => { ...... // 注册快捷键 globalShortcut.register(CommandOrControl+X, () => { console.log(CommandOrControl + X is pressed) }) globalShortcut.register(CommandOrControl+M, () => { mainWindow.maximize() }) globalShortcut.register(CommandOrControl+T, () => { mainWindow.unmaximize() }) globalShortcut.register(CommandOrControl+H, () => { mainWindow.close() }) // 检查快捷键是否注册成功 // console.log(globalShortcut.isRegistered(CommandOrControl+X))} // 将要退出时的生命周期,注销快捷键app.on(will-quit, () => { // 注销快捷键 globalShortcut.unregister(CommandOrControl+X) // 注销所有快捷键 globalShortcut.unregisterAll()})

7.2在渲染进程中定义

通过retmote来定义

const { globalShortcut } = window.require("@electron/remote") globalShortcut.register("Ctrl+O", () => { console.log(ctrl+O is pressed.)})

8. 主进程和渲染进程通讯

在渲染进程使用ipcRenderer,主进程使用ipcMain,可以实现主进程和渲染进程的通讯:

App.tsx

......import React,{ useState, useRef } from reactconst { shell, ipcRenderer } = window.require(electron)export default function App() { // state const [windowSize, setWindowSize] = useState(max-window) ...... // 传参 const maxWindow = () => { ipcRenderer.send(max-window, windowSize); if(windowSize === max-window) { setWindowSize(unmax-window) } else { setWindowSize(max-window) } } ...... return ( <div> <div> <button onClick={maxWindow}>与主进程进行通讯,窗口最大化或取消窗口最大化</button> </div> </div> </div> )}

main.js

const {app, BrowserWindow, nativeImage, globalShortcut, ipcMain } = require(electron)const createWindow = () => { let mainWindow = ...... ...... // 定义通讯事件 ipcMain.on(max-window, (event, arg) => { if(arg === max-window) { mainWindow.maximize() } else if (arg === unmax-window) { mainWindow.unmaximize() } }) ......}......

原文链接:Electron结合React和TypeScript进行开发 - bleaka - 博客园

排行

网站地图

Copyright © 2002-2022 香港德胜云网络 版权所有 | 备案号:蜀ICP备2023007363号-5

声明: 本站内容全部来自互联网,非盈利性网站仅供学习交流