electron
Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux——不需要本地开发 经验。
react
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
antd
一个非常流行的前端UI框架
安装electron
npm install electron --save-dev
修改国内镜像
npm config set registry https://registry.npm.taobao.org
查询是否成功修改
npm config get registry
下载 cnpm 全局使用
npm install -g cnpm --registry=https://registry.npm.taobao.org
如果无法使用 cnpm
1.进⼊node.js莫⽬录找到cnpm⽂件的位置将其移动到于npm⽂件的同⼀⽂件夹下
2.再将cnpm和cnpm.cmd⽂件移⾄npm与npm.cmd所在的⽂件夹即可解决问题
electron 配置
{
"name": "electron_gui",
"version": "1.0.0",
"description": "",
"main": "main.js", // 入口文件
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"electron": "electron ." // 主electron 执行程序
},
"author": "",
"license": "ISC",
"dependencies": {
"electron": "^19.0.5"
}
}
热加载
cnpm i --save-dev electron-reloader
main.js
const { app, BrowserWindow ,Menu } = require('electron')
// 热加载
const reloader = require('electron-reloader')
reloader(module)
// app 窗口启动
app.on('ready', ()=>{
// 创建窗口指定高和宽度
const mainWindow = new BrowserWindow({
width:500,
height:500
})
// 加载 html 文件 会渲染到 窗口内
mainWindow.loadFile('./src/index.html')
// 直接打开 调试 或者 ctrl+shift+i 、 mac 调试用 openDevTools
mainWindow.webContents.openDevTools()
// 自定义模板 - 菜单栏
const template = [
{
label: '文件',
submenu:[
{
label: '新建窗口'
}
],
},
{
label: '关于'
}
]
// 编译模板
const menu = Menu.buildFromTemplate(template)
// 设置菜单
Menu.setApplicationMenu(menu)
})
在窗口中开发调试面板
windows
ctrl+shift+i
mac
mainWindow.webContents.openDevTools()
通过 template 模板加载生成 menu 菜单
const template = [
{
lable: '文件',
// 子菜单
submenu:[
{
label: '新建窗口'
}
]
},
{
lable: '关于'
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMeun(menu)
加载主进程的代码
const { remote: { BrowserWindow } } = require('electron')
在css 里面的拖拽事件
使用拖拽
-webkit-app-region: drag;
禁用拖拽
-webkit-app-region: no-drag;
const {remote:{BrowserWindow} , shell } = require('electron')
点击新建窗口, 创建新窗口
const newWindow = document.querySelector('.new-windon')
newWindow.onclick = function(){
new BrowserWindow({
width: 300,
heigth: 300
})
}
// 点击 a 标签跳转,shell 使用外部浏览器打开窗口
const allA = document.querySelectorAll('a')
allA.forEach(item =>{
item.onclick = function(e){
// 阻止默认事件
e.preventDefault()
shell.openExternal(item.herf)
}
})
通过 frame:false 创建无边框窗口
渲染进程和主线程通信
定义按钮
<div class="maxWindow no-drag" onclick="maxWindow()"></div>
事件函数
function maxWindow(){
ipcRenderer.send('max-window')
}
主线程定义事件
ipcMain.on('max-window',()=>{
mainWindow.maximize()
})
传参
let windowSize = 'unmax-window'
function maxWindow(){
windowSize = windowSize === 'max-window'?'unmax-window':'max-window'
ipcRenderer.send('max-window', windowSize)
}
接受参数
ipcMain.on('max-window',(event, arg)=>{
console.log(arg)
if (arg === 'unmax-window') return mainWindow.maximize();
mainWindow.unmaximize()
})
electron 打包
安装electron-packager
cnpm i electron-packager -D
在 package.json里面添加
"build": "electrion-packager ./ appname --platform=win32 --arch=x64 --out ./outApp --overwrite --icon=./log.ico"
react 要操作系统代码,需要通过node 的 child_process 的exec 执行命令。
需要和进程通信,渲染进程->主进程
不能用import引入进来,也不能直接用require,
以下三种方法都会导致报错:
import { ipcRenderer } from 'electron'
import electron from 'electron'
const electron = require('electrion')
是因为:
require/exports 和 import/export 形式不一样,遵循的模块化也不一样。
require/exports是一种野生的规范。
require/exports 的用法只有以下三种简单的写法:
const fs = require('fs')
exports.fs = fs
module.exports = fs
而 import/export 的写法就多种多样:
import fs from 'fs'
import {default as fs} from 'fs'
import * as fs from 'fs'
import {readFile} from 'fs'
import {readFile as read} from 'fs'
import fs, {readFile} from 'fs'
export default fs
export const fs
export function readFile
export {readFile, read}
export * from 'fs'
正确引入
const electron = window.require('electron');
const {ipcRenderer} = electron;
console.log(ipcRenderer)
ipcRenderer.send('ipcSignal','hello world!');
通过react 页面前端和底层系统交互 windows
官方例子
// On Windows Only...
const { spawn } = require('node:child_process');
const bat = spawn('cmd.exe', ['/c', 'my.bat']);
bat.stdout.on('data', (data) => {
console.log(data.toString());
});
bat.stderr.on('data', (data) => {
console.error(data.toString());
});
bat.on('exit', (code) => {
console.log(`Child exited with code ${code}`);
});
// OR...
const { exec, spawn } = require('node:child_process');
exec('my.bat', (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
// Script with spaces in the filename:
const bat = spawn('"my script.cmd"', ['a', 'b'], { shell: true });
// or:
exec('"my script.cmd" a b', (err, stdout, stderr) => {
// ...
});
自己实现
页面按钮功能
const handleCmd = ()=>{
// react -> nodejs -> system
ipcRenderer.send("svnUpdate")
}
ipcRenderer.on('readSvndata',(event,arg)=>{
// arg 才是返回的数据
console.log("event: ",event, arg)
alert(arg)
})
election -> main.js
const { ipcMain } = require('electron')
const {exec} = require('child_process')
// 监听渲染进程 node执行exec函数
ipcMain.on('svnUpdate',(event,arg)=>{
exec("svn up $PATH", (err, stdout, stderr) => {
mainWindow.webContents.send('readSvndata',stdout)
})
})
react + antd
先安装react - 18
npm install -g create-react-app
npm install -g react-dom
修改package.json配置文件的scripts
// 指定electron入口文件
"main": "main.js",
"homepage": ".",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
// 启动程序
"electron": "electron ."
},
再在根目录里安装 electron
npm install electron --save-dev
新建一个main.js 用于配置 electron
// 引入electron并创建一个Browserwindow
const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')
// 保持window对象的全局引用,避免JavaScript对象被垃圾回收时,窗口被自动关闭.
let mainWindow
function createWindow () {
//创建浏览器窗口,宽高自定义具体大小你开心就好
mainWindow = new BrowserWindow({width: 800, height: 600})
/*
* 加载应用----- electron-quick-start中默认的加载入口
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, './build/index.html'),
protocol: 'file:',
slashes: true
}))
*/
// 加载应用----适用于 react 项目
mainWindow.loadURL('http://localhost:3000/');
// 打开开发者工具,默认不打开
// mainWindow.webContents.openDevTools()
// 关闭window时触发下列事件.
mainWindow.on('closed', function () {
mainWindow = null
})
}
// 当 Electron 完成初始化并准备创建浏览器窗口时调用此方法
app.on('ready', createWindow)
// 所有窗口关闭时退出应用.
app.on('window-all-closed', function () {
// macOS中除非用户按下 `Cmd + Q` 显式退出,否则应用与菜单栏始终处于活动状态.
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// macOS中点击Dock图标时没有已打开的其余应用窗口时,则通常在应用中重建一个窗口
if (mainWindow === null) {
createWindow()
}
})
// 你可以在这个脚本中续写或者使用require引入独立的js文件.
热加载
cnpm i --save-dev electron-reloader
react
react 框架核心
reactDom 专门做渲染
index.css 全局样式
./App 引入根组件
ReactDom.render 渲染根组件APP 到root dom节点上
react 18 问题
严格模式关闭
列表渲染
使用map
遍历列表时同样需要一个类型为number/string不可重复的key,提高diff性能
key 仅在内部使用,不会出现在真实的dom结合中
{ var.ma p( v=> <li key={v.id} > {v.name} </li>) }
jsx 条件渲染
可以用 三元运算符,逻辑&&运算
{ bool && <span>123</span> }
行内样式 在元素内绑定
<span style={{color:'red',fontSize:'30px'}} >123</span>
类名样式
const style = {color:'red',fontSize:'30px'}
<span style={style} >123</span>
// css 样式
<span className='active' >123</span>
jsx 注意事项
1. jsx 必须有一个根节点,如果没有根节点,可以使用<></>代替
2. 所有标签必须闭合
3. jsx中使用驼峰命名,className
4. jsx 支持多行换行,如果需要换行,需要使用()包裹,防止bug
函数组件约定
1.组件名称必须大写,react内部会判断是组件还是普通标签
2.函数组件必须有返回值,不渲染则返回 null
3.组件像标签一样可以被渲染到页面内,组件表示一段结构内容,
4. 可以<Hello/> 也可以 <Hello> </Hello>
类组件创建渲染
import react
class HellComponent extends React.Component{
reder(){
return <div>test class</div>
}
}
1.类名称必须大写字母开头
2.类组件应该继承React.Component父类,可以获取父类中的方法属性
3.类组件必须提供render方法render方法必须有返回值,表示UI结构
事件绑定
语法
on + 事件名称={事件处理程序}
如
总结
1.编写组件就是编写原生js类或者函数
2.定义状态必须通过state实例属性的方法提供一个对象,名称固定 state
3.修改state中的任何属性都不可以通过直接赋值必须走 setState方法,是因为继承得到的
4.这里的this关键词
组件通讯
父传子
1.父组件提供传递的数据 - state
2.给子组件标签'添加属性'值为state中的数据
3.子组件中通过props接受父组件中传过来的数据
- 1.类组件使用this.props获取props对象
- 2.函数式组件直接通过参数获取props对象
1.props是只读对象
根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改
2.props可以传递任意数据
数值,字符串,布尔值,数组,对象,函数,jsx
子传父:子组件调用父组件传递过来的函数,并把想要传递的数据当成函数实参
1.先在父组件内创建函数
2.把函数传到子组件内
3.子组件内调用,即可把子组件的数据传递给父组件
跨组件通信 Context
1.创建Context对象,导出provider和Consumer对象
const {Provider, Consumer} = createContext()
2.使用Provider包裹根组件提供数据
<Provider value={this.state.message}
...根组件
</Provider>
3.需要用到数据的组件使用Consumer包裹获取数据
<Consumer>
{ value => 基于context值进行渲染 }
</Consumer>
生命周期
图示生命周期
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
constructor:
创建组件时,最先执行,初始化的时候只执行一次
render:
每次组件渲染都会触发,不能调用setState()操作,否则无限调用
componentDidMount:
组件挂载(完成DOM渲染)后执行,初始化的时候执行一次
hooks
使函数拥有自己的状态
为函数组件提供状态
只能在函数组件中使用
useState
1. 导入useState函数 react
2. 执行这个函数并且传入初始值必须在函数组件汇总
3. [数据, 修改数据的方法]
4. 使用数据 修改数据
const [count, setCount] = useState(0)
1. useState 传过来的参数 作为count的初始值
2. [count, setCount] 这里的写法是一个解构赋值,useState返回值是一个数组
顺序不可换。第一参数就是数据状态,第二参数就是修改数据的方法
3.setCount函数作用用来修改count依旧保持不能直接修改原值还是生成新值替换原值
setCount(基于原值计算得到的新值)
4.count和setCount是一对 是绑定一起的 setCount只能用来修改对应的count值
组件的更新
调用setCount的时候更新过程
首次渲染 组件内部代码会被执行一次
其中useState也会跟着执行,这里重点注意,初始值只在首次渲染时生效
更新渲染 setCount 都会更新
1. app组件会再次渲染 这个函数会再次执行
2. useState再次执行 得到的新的count值不是而是修改之后的1,模板会用新值
useState初始值只要在此渲染生效,后续只要调用setCount整个app中代买都会执行
此时的count每次拿到的都是最新值时的count每次拿到的都是最新值
useState 注意事项
1. 只能出现在函数中
2. 不能嵌套在if/for 其他函数中
react 按照hooks的调用顺序识别每一个hook
useEffect
做副作用处理
1. 导入useEffect 函数
2. 子啊函数组件中执行,传入回调,并定义副作用
3. 当我们通过修改状态更新组件时,副作用也会不断执行
依赖项控制副作用的执行时机
1.默认状态(无依赖相)
组件初始化的时候先执行一次,等到每次数据修改组件更新再次执行
2. 添加一个空数组依赖性
组件初始化的时候执行一次
3. 依赖特定相
组件初始化的适合执行一次,依赖的特定项发生变化会再次执行
useEffect(()=>{
document.title = count
},[count])
注意事项
useEffect 回调函数中用到的数据(比如,count)就是依赖数据
就应该出现在依赖数组中,如果不添加依赖性会出BUG
hook的出现,就是不用生命周期的概念也能实现业务代码
初始化 或者变量被修改时 都会执行
useEffect - 清理副作用
function Test(){
useEffect(()=>{
let timer = setInterval(()=>{
console.log('定时器执行')
},1000)
return ()=>{
// 清理动作
clearInterval(timer)
}
},[]) // [] 代表初始化只执行一次
return(
<div>this is test!</div>
)
}
发送网络请求
useEffect
1. 不加依赖性 - 初始化 + 重新渲染
2. 加[] - 初始化一次
3. 加特定依赖项 [count, name] - 首次执行+任意一个值的变化执行
!!!真实DOM渲染之后才会执行useEffect !!!
在内部单独定义一个函数,然后把这个函数包装成同步
useEffect(()=>{
async function fetchData(){
const res = await axios.get('url')
console.log(res)
},[])
可以写多次 useEffect
原生 fetch web api
搜索 fetch mdc
fetch('url')
.then( response => response.json() )
.then( data => console.log(data) )
useRef
在函数组件中获取真实的dom元素对象,或者是组件对象
1. 导入 useRef 函数
2. 执行 useRef 函数并出传入null,返回值为一个对象
内部有一个current属性存放拿到的DOM对象, (组件实例)
3. 通过ref绑定 要获取的元素或者组件
组件实例 类组件
dom对象 标签
import { useEffect, useRef } from 'react'
function App(){
const h1Ref = useRef(null)
useEffect(()=>{
console.log(h1Ref)
},[])
return(
<div>
<h1 ref={ h1Ref }>这是h1标签</h1>
</div>
)
}
useConext
context如果要传递的数据 只需要在整个应用初始化的时候传递一次就可以
就可以选择在当前文件里做数据提供, index.js 静态的
如果Context需要传递数据并且将来还需要在数据做修改,底层组件也需要一起改变
需要写到app.js , 动态的
useState
1.导入useState函数 react
2.执行这个函数并且传入初始值必须在函数组件汇总
3.[数据, 修改数据的方法]
4.使用数据 修改数据
状态读取和修改
const [count, setCount] = useState(0)
1. useState 传过来的参数 作为count的初始值
2. [count, setCount] 这里的写法是一个解构赋值,useState返回值是一个数组
顺序不可换。第一参数就是数据状态,第二参数就是修改数据的方法
3.setCount函数作用用来修改count依旧保持不能直接修改原值还是生成新值替换原值
setCount(基于原值计算得到的新值)
4.count和setCount是一对 是绑定一起的 setCount只能用来修改对应的count值
组件的更新
调用setCount的时候更新过程
首次渲染 组件内部代码会被执行一次
其中useState也会跟着执行,这里重点注意,初始值只在首次渲染时生效
更新渲染 setCount 都会更新
1. app组件会再次渲染 这个函数会再次执行
2. useState再次执行 得到的新的count值不是而是修改之后的1,模板会用新值
useState初始值只要在此渲染生效,后续只要调用setCount整个app中代买都会执行
此时的count每次拿到的都是最新值
useCallback 避免重复渲染 可以用在 webSocket
用于得到一个固定引用值的函数,通常用它进行性能优化
该函数有两个参数:
1.函数useCallBack会固定该函数的引用,只要依赖项没有发生改变,则始终返回之前函数的地址
2.数组,记录依赖项(类似于useEffect)
该函数返回:引用相对固定的函数地址
useLayoutEffect(布局副作用)
布局副作用
- useEffect 在浏览器渲染完成 后 执行
- useLayoutEffect 在浏览器渲染 前 执行
特点:
- useLayoutEffect 总是比 useEffect 先执行
- useLayoutEffect 里面的任务最好影响了Layout(布局)
代码演示
import React, {useState, useLayoutEffect, useEffect} from "react";
import ReactDOM from "react-dom";
function App() {
const [n, setN] = useState(0)
const onClick = ()=>{
setN(i=>i+1)
}
useEffect(()=>{
console.log("useEffect")
})
useLayoutEffect(()=>{ // 改成 useEffect 试试
console.log("useLayoutEffect")
})
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={onClick}>Click</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
注意:为了用户体验最好优先使用useEffect
项目开始
脚手架: create-react-app
钩子: react hooks
状态管理: mobx
UI: antd v4
ajax: axios
路由: react-router-dom 和 history
css: sass
npx create-react-app appname
目录结构
store: 存放mobx -> index.js
utils: 工具 -> index.js
style: 样式 -> index.css
hooks: 钩子
pages: 路由
components: 通用组件
SCSS预处理器
react支持sass
SASS 是一种预编译的CSS,作用类似于less
由于react中内置处理了SASS的配置,所以在CRA创建项目中可以直接使用SASS写样式
安装 - 在dev 环境中使用
yarn add sass -D
配置路由
yarn add react-router-dom
配置路由
Layout 和 Login
function Layout() {
return(
<div>layout</div>
)
}
export default Layout
app.js
import {BrowserRouter, Routes, Route} from "react-router-dom"
import Layout from "./pages/Layout";
import Login from "./pages/Login";
function App() {
return (
// 路由配置
<BrowserRouter>
<div className="App">
<Routes>
{/*创建路由path和组件规则*/}
<Route path='/' element={<Layout/>}>index</Route>
<Route path='/login' element={<Login/>}>login</Route>
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
antd
yarn add antd
index.js
// 导入这个包 以免报错
import 'antd/dist/antd.min.css'
// 最后导入样式文件 以免被覆盖
import './index.scss';
别名路径
能够配置@路径 简化路径处理
craco 配置文件
1. CRA将所有工程化配置,都隐藏在react-scripts包中,所以项目中看不到任何配置信息
2. 如果要修改CRA的默认配置
- 通过第三方库来修改 比如 @craco/craco 推荐
- 通过执行 yarn eject 命令,释放 react-scripts 中的所有配置到项目中
1.安装
yarn add -D @craco/craco
2.在根项目目录中创建craco的配置文件
craco.config.js
并在配置文件中配置路径别名
3.修改package.json 中的脚本命令
4. 在代码中就可以通过 @ 来表示src目录的绝对路径
5. restart 生效
//添加自定义 webpack配置
const path = require('path')
module.exports={
//webpack
webpack:{
// 别名配置
alias:{
// 约定使用 @ 表示 src 文件所在路径
'@': path.resolve(__dirname, 'src')
}
}
}
package.json 修改 react-scripts -> craco
"start": "craco start",
"build": "craco build",
"test": "craco test",
remember
<Form
initialValues={{
remember: true,
}}
>
<Form.Item
name="remember"
valuePropName="checked"
>
axios
拦截器
yarn add axios
mobx
yarn add mobx mobx-react-lite
跨域
yarn add http-proxy-middleware
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
// const url = `http://pv.sohu.com/cityjson?ie=utf-8`;
// let promise = http.get(url);
// console.log(promise)
app.use(proxy('/api/', {
target: 'http://127.0.0.1:9001',
changeOrigin: true,
ws: true,
// headers: {'X-Real-IP': promise['cip']},
headers: {'X-Real-IP': "1.1.1.1"},
pathRewrite: {
'^/api': ''
}
}))
};
使用 application/x-www-form-urlencoded format
默认情况下,axios将JavaScript对象序列化为JSON。
要以application / x-www-form-urlencoded格式发送数据,您可以使用以下选项之一。
最简单实用qs内置编码
const qs = require('qs');
axios.post('url', qs.stringify({data}))
路由鉴权
1. 判断token 是否存在
2. 如果存在直接渲染
3. 如果不存在重定向到登录路由
高阶组件
把一个组件,当另外一个组件的参数传入
然后通过一定的判断,返回新组件
import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'
function AuthComponent({ children }){
const isToken = getToken()
if (isToken){
return <>{children}</>
}eles{
return <Navigate to="/login" replace />
}
}
export {
AuthComponent
}
useNavigate()
所有带 use 是钩子函数,也只能在函数组件内 使用,
安装history 包
yarn add history
要使用 cookie 需要安装包
yarn add react-cookies
import cookie from 'react-cookies'
//设置cookie,第三个参数的意思是所有页面都能用这个cookie
cookie.save(key,value,{path:"/"})
// 加载名为cookieName的cookie信息
cookie.load(cookieName)
// 删除名为cookieName的cookie信息
cookie.remove(cookieName)
mobx 6
yarn add mobx mobx-react-lite # 仅限函数式组件使用
概念
observable 定义一个存储state的可追踪字段(Proxy)
action 将一个方法标记为可以修改的state的action
computed 标记一个可以由state派生出新值并且缓存其输出的计算属性
监听属性
autorun 函数接受一个函数作为参数,每次当函数所观察的值被发生变化时,他都运行
当自己创建autorun时,他只会运行一次
mobx会自动收集并订阅所有的可观察属性,一旦有改变发生,autorun将会再次触发
autorun(()=>{
console.log('counter.count', counter.count)
})
reaction 的使用
reaction 类似于autorun 但可以让你更加精细地控制要跟踪的可观察对象
它接受两个函数作为参数
1。data函数,起返回值将会作为第二个函数输入
2。回调函数
与autorun不同,reaction在初始化时不会自动运行
reaction(
()=> count.count
(v,oldv)=>{
console.log('变化了',v,oldv)
}
)
异步处理
异步进程中mobx中不需要任何特殊处理,因为不论是何时引发的所有reactions都将会自动更新
因为可观察是可变的,因此在action执行过程中保持对她们的引用一般是安全的
如果可观察对象的修改不是在action函数中,控制台会报警告
标签:count,const,函数,react,electron,组件,antd
From: https://www.cnblogs.com/huidou/p/17240937.html