前言

本文是为了记载作者暑期实习学习到的常识,对其进行了总结和思考,共享出来,期望则能够协助到像作者一样仍是小白的uu。

开发者东西(Chrome)

首要是一些谷歌的一些快捷键,刚开始用的时分,记不住很正常,多用几回就好啦。

操作 windows快捷键
翻开一个新窗口 ctrl+n
翻开新的标签页并跳转到新的标签页 ctrl+t
跳转到特定标签页 ctrl+1到ctrl+8
跳转到最终一个标签页 ctrl+9
翻开其时标签页浏览记载中记载的上\下一个页面 alt+向左箭头/alt+向右箭头
封闭其时窗口 ctrl+w
跳转到下一个翻开的标签页 Ctrl + Tab
跳转到上一个翻开的标签页 Ctrl + Shift + Tab

明晰的知道操控台下各个模块的效果,对代码的调试是很有好处的。这里贴一个链接,便利今后检查。

https:///book/6844733783166418958/section/6844733783187390477

whistle

以我现在的水平,首要仍是运用whistle调配SwitchyOmega对接口进行测验以及署理。其他功用还需求在今后的开发过程中慢慢深化的学习。

测验接口的流程

  1. w2 start发动whistle

实习总结

  1. 新建规矩,并应用规矩。

实习总结

  1. 新建values,这一步不是有必要的,仅仅为了以防后端接口还没写好,但此刻又想测验接口,咱们局能够自己新建数据文件,当拜访接口时,回来咱们事先准备好的数据。

实习总结

  1. 检查恳求情况

实习总结

whistle的装置流程甩个链接在这里: /post/686188…

git的运用

git下载

https://blog.csdn.net/rej177/article/details/126998371

新建一个Git的两种办法

1.本地新建好 Git 项目,然后相关长途库房

# 初始化一个Git库房
git init
# 相关长途库房
git remote add <name> <git-repo-url>
# 例如:
git remote add origin https://github.com/xxxxxxxxx

2.clone长途库房

# 在长途新建好库房,然后clone到本地
git clone <git-repo-url>
# 将长途库房下载到(其时 git bash 发动位置下面的)指定文件中,假如没有会主动生成
git clone <git-repo-url> <project-name>

clone库房代码

在运用ssh进行clone之前,还需求进行如下装备:

  • 在bash.exe中生成密钥:ssh-keygen -t ed25519 -C "你自己的邮箱",一路回车完结密钥的生成。
  • 后台发动ssh署理:eval “$(ssh-agent -s)”
  • 增加密钥到对应账号的ssh密钥中(我运用的是gitee),能够经过输入cat ~/.ssh/id_ed25519.pub 获取ssh密钥,也能够经过记事本翻开id_ed25519.pub文件,将内容悉数仿制并粘贴。

1.运用https进行clone

  • 先创立一个文件夹用来接纳克隆下来的代码

  • cmd—>运用指令git clone url

  • 代码克隆完结

2.运用ssh进行clone

  • 创立文件夹用来接纳克隆修正来的代码

  • cmd—>运用指令git clone url

  • 代码克隆完结

  • 运用ssh克隆代码报错

  • 运用git拉代码时报错: Unable to negotiate with **** port 22: no matching host key type found.

原因:或许是新的ssh客户端不支持ssh-rsa算法,要修正本地装备从头运用ssh-rsa算法。
处理办法:在生成公钥的文件夹里(一般在其时用户目录下的.ssh文件中)创立一个config文件(没有后缀),用文本文档格局翻开,增加下方内容
Host *
HostkeyAlgorithms +ssh-rsa
PubkeyAcceptedKeyTypes +ssh-rsa

一些常用的git指令

设置用户名和邮箱

git config --global user.name "John Doe"
git config --global user.email johndoe@example.com

复制库房代码

git clone url

分支相关的指令

# 检查本地一切的分支
git branch
# 检查长途一切的分支
git branch -r
# 检查一切的分支,本扩本地分支和长途分支
git branch -a
# 切换分支
git checkout branchName
# 将本地库房文件推送到指定的长途分支
git push -o origin branchName

回滚操作

# 把指定文件从暂存区回滚到作业区
git restore --staged <file>
# 把指定文件的更改吊销
git restore <file>
# 回滚到最近一次提交的上一个版别
git checkout HEAD^
# 回滚到最近一次提交的上两个版别
git checkout HEAD^^

上面总结的是作者用的比较多的指令,更多用法能够去检查下面的文章,写的十分详细。

https:///post/6844904191203213326#heading-26

TypeScript

1.Typescript开发环境建立

1.下载node.js

2.装置node.js

3.运用npm大局装置typescript

  • 进入指令行
  • 输入npm i -g typescript

4.创立一个ts文件

5.运用tsc对ts文件进行编译

  • 进入指令行
  • 进入ts文件地点目录
  • 履行指令:tsc xxx.ts

2.ts的类型声明

非函数中的变量声明

语法:

let 变量名:变量类型=值

假如变量的声明和赋值是一同进行的,TS能够主动对变量进行类型检测。

函数中的类型声明

语法:

function fn(a:数据类型,b:数据类型,……):回来值的数据类型{
    //函数操作
}

3.数据类型

类型 比方 描绘
number 1,-33,2,5 恣意数字
string ‘hi’,’hello’ 恣意字符串
boolean true,false 布尔值true或false
字面量 其本身 约束变量的值便是该字面量的值
any * 恣意类型
unknown * 类型安全的any
void 空值(undefined) 没有值(或undefined)
never 没有值 不能是任何值
object {title:’傍晚下的蓝玫瑰’} 恣意的JS方针
array [1,2,3] 恣意的JS数组
tuple [4,5] 元素,TS新增类型,固定长度数组
enum enum{A,B} 枚举,TS中新增类型
  • 运用字面量进行类型声明
let a:10;
a=10;
a=11;//报错
let b:"male" | "female"
//b的值只能为male或female之一
  • 联合类型
let c:boolean | string
c=true
c='傍晚下的蓝玫瑰'
  • any 表明是任何数据类型,一个变量设置为any后相当于对该变量封闭了ts的类型检测
let d:any;
d=1;
d=true;
d='傍晚下的蓝玫瑰'
//声明变量假如不指定类型,则TS解析器会主动判别变量的类型为any(隐式的any)
let d;
d=1;
d=true;
d='傍晚下的蓝玫瑰'
  • unknown 未知数据类型
let e:unknown
e=1;
e=true;
e='傍晚下的蓝玫瑰'
  • any和unknown的差异
let s:string;
//不报错,d的类型为any,它能够赋值给恣意变量,导致该变量也被逼封闭类型检测
s=d;
//报错
s=e;
//unknown类型的变量,不能直接赋值给其他变量
//处理方案:
//1.进行类型检测
if(typeof e === 'string'){
    s=e
}
//2.类型断语
//告知解析器变量的实践类型
s=e as string || s=<string>e
语法:
	变量 as 数据类型
	<数据类型>变量
  • void 一般用于函数,用来表明空,表明函数没有回来值
  • never 一般用于函数,表明永远不会回来成果
function fn2():never{
    throw new Error('error')
}
  • object
//对特色类型进行约束
let b:{name:string,age?:number}
//在特色名后加?表明特色可选
b={title:'傍晚下的蓝玫瑰'}
//当咱们需求对方针的特色进行动态增加时,不或许每次都是加上?,这时咱们能够采纳以下的办法进行声明
let c:{name:string,[propName:string]:any};
//意思便是:方针里有必要有name特色,其他特色可有可无。
  • 对函数结构进行约束
//语法:(形参:数据类型,形参:数据类型,……)=>回来值数据类型
let d:(a:number,b:number)=>number
d=function(n1,n2){
    return n1+n2
}
  • array
/*
    数组的类型声明
    类型[]  ||   Array<类型>
*/
let arr1:string[]
arr1=['hello','world']
let arr2:Array<number>
arr2=[1,2,3,4]
  • tuple 元组,固定长度的数组
let h:[string,string];
//表明,一个元组里面只需两个数据,两个数据的数据类型都为string
  • enum 枚举
enum Gender{
    male,
    female
}
let i:{name:string,gender:Gender};
i={
    name:'陈路周',
    gender:Gender.male
}
console.log(i.gender===Gender.male)
&:一同
let j:string & number
let j:{name:string} & {age:number}
j={name:'陈路周',age:20}
  • 类型别号
//给string起了个别号
type myType=string 
type myType=1|2|3|4|5
let k:myType

3.编译选项

翻开监督形式

tsc 文件名.ts -w
//但这种办法,只能一同监听一个文件
//处理方案:创立tsconfig.json
tsc -w 
//一同监听多个ts文件

tsconfig.json中的装备项

尽管说运用脚手架初始化一个项目时,会主动帮咱们增加一些装备,但为了更好的开发,也为了更好的排错,关于ts的一些常用装备仍是要了解清楚的。

顶层装备项:include、exclude、files、compilerOptions

  • include:指定哪些ts文件需求被编译
*表明恣意文件,**表明恣意目录
  • exclude:指定不需求被编译的文件
  • files:指定被编译的文件(数组格局,一个一个文件名)
  • compilerOptions:编译器选项
    • target:指定ts被编译为的ES的版别
    • module:指定运用的模块化标准
    • lib:指定项目中需求运用的库
    • outDir:指定编译后的文件放置的位置
    • outFile:将代码合并为一个文件,设置outFile后,一切的大局效果域中的代码会被合并到同一个文件中
    • allowJs:是否对js文件进行编译,默许不编译
    • checkJs:检查js代码是否符合语法标准,默许是false(不检查)
    • removeComments:是否移除注释
    • noEmit:不生成编译后的文件
    • noEmitOnError:当有过错时不生成编译后的文件
    • alwaysStrict:用来设置编译后的文件是否运用严格形式
    • noImplicitAny:不答应运用隐式的any类型
    • noImplicitThis:不答应不明确类型的this
    • strictNullChecks:严格检查空值
    • strict:一切严格检查的总开关 建议设置为true

4.运用webpack打包ts文件

通常情况下,实践的开发中咱们都需求运用构建东西对代码进行打包,TS相同也能够结合构建东西一同运用,下边以webpack为例介绍以下如何结合构建东西运用TS。

根底用法

  • 步骤

    1.初始化项目

    • 进入项目根目录,履行指令npm init -y
      • 首要效果:创立package.json文件

    2.下载构建东西

    • npm i -D webpack-cli webpack typescript ts-loader

    3.在项目根目录下创立webpack.config.js文件

    • 在webpack.config.js文件中增加装备项
      • entry:指定进口文件
      • output
        • path:指定打包文件的目录path.resolve(__dirname,'dist')
        • filename:打包后的文件bundle.js
      • module:模块
        • rules:打包规矩
          • loader的履行次序为:普通loader履行的次序是从右向左,从下往上
          • test:指定需求运用use中模块进行处理的文件/\.ts$/
          • use:指定处理模块ts-loader
          • exclude:指定不需求进行处理的文件/node-modules/

    4.在package.json文件中的scripts装备项中参加"build": "webpack"

    5.履行npm run build进行打包

优化装备

html-webpack-plugin

html-webpack-plugin的首要效果便是在webpack构建后生成html文件,一同把构建好进口js文件引进到生成的html文件中。 装备办法如下:

//先在顶层引进html-webpack-plugin
const HtmlWebpackPlugin=require('html-webpack-plugin')
//在module.exports={}顶层增加装备信息
module.exports={
    plugins:[
        new HtmlWebpackPlugin({
 		    //模版途径
            template:'./src/index.html'
 	    }) 
    ]
}

clean-webpack-plugin

在每次打包发布时主动清理掉 dist 目录中的旧文件

//先在顶层引进插件
const {CleanWebpackPlugin}=require('clean-webpack-plugin')
//在module.exports={}顶层增加装备信息
module.export={
    plugins:[
        new CleanWebpackPlugin()
    ]
}

运用引用模块时报错

报错信息:D:\WPSTest\StydyTest\ts\src\m1 doesn’t exist 处理方案如下:

//在module.exports={}顶层增加装备信息
module.export={
    //声明以js和ts结尾的文件能够被引用
    resolve:{
        extensions:['.ts','.js']
    }
}

处理代码在浏览器兼容的问题

一些低版别的浏览器或许不支持es6的新语法,这是咱们就需求借助babel来处理兼容问题

1.装置插件

npm i -D @babel/core @babel/preset-env babel-loader core-js

2.增加装备信息

rules:[
    {
        test:/\.ts$/,
        use:[
        	//装备babel
        	{
                //指定加载器
                loader:"babel-loader",
                //设置babel
                options:{
					//设置预界说的环境
					presets:[
                        [
                            //指定环境插件
                            " @babel/preset-env",
                            //装备信息
                            {	
                            	//要兼容的方针浏览器
                                targets:{
                                    //指定浏览器版别
                                    "chroms":"88"
                                },
                                //指定corejs的版别
                                "corejs":"3",
                                //运用corejs的办法  "usage"表明按需加载
                                useBuiltIns:"usage"
                            }
                        ]
					]
				}
        	}
            'ts-loader'
        ]
    }
]

后续咱们只需将想要兼容的浏览器版别增加到targets中就能够了

environment

告知webpack不适用箭头函数(增加这个装备项是因为其时想兼容不支持ES6语法的浏览器,比方IE)

//在顶层module.export={}参加以下装备信息
environment:{
	arrowFunction:false
}

5.面向方针

  • 书写格局
class Dog{
    name:string;
    age:number
    constructor(name:string,age:number){
        this.name=name
        this.age=age
    }
    //界说在实例上的办法
    sayHello(){
        console.log('汪汪汪')
    }
}
  • 承继
    • 能够在不修正原来类的根底上,增加新的功用。
    • 完成:界说一个新类,承继旧类,将需求拓宽的新办法和特色界说在新类中
    • 办法重写:假如在子类中增加了和父类相同的办法,则子类办法会覆盖掉父类的办法
class Animal{
    name:string;
    age:number;
    constructor(name:string,age:number){
        this.name=name
        this.age=age
    }
    sayHello(){
        console.log('汪汪汪')
    }
}
class Dog extends Animal{
    run(){
        console.log(this.name+'is keeping running')
    }
}
class Cat extends Animal{
}
let cat=new Cat('咪咪',3)
let dog=new Dog('旺旺',5)
dog.run()
  • super

应用场景:拓宽新特色时

class Cat extends Animal{
    gender:number;
    constructor(name:string,age:number,gender:number){
        super(name,age)
        this.gender=gender
    }
}
let cat=new Cat('咪咪',3,1)
let dog=new Dog('旺旺',5)
dog.run()
console.log(cat)
  • 笼统类
    • 以abstract最初的类是笼统类
    • 笼统类和其他类的差异不大,仅仅不能用来创立方针
    • 笼统类便是专门用来被承继的
  • 笼统办法
    • 笼统类中能够增加笼统办法(以abstract最初)
    • 笼统办法只能界说在笼统类中,且子类有必要对笼统办法进行重写
abstract class Animal{
    name:string;
    age:number;
    constructor(name:string,age:number){
        this.name=name
        this.age=age
    }
    abstract sayHello():void
}
class Dog extends Animal{
    sayHello(): void {
        console.log('汪汪汪')
    }
    run(){
        console.log(this.name+'is keeping running')
    }
}
class Cat extends Animal{
    gender:number;
    constructor(name:string,age:number,gender:number){
        super(name,age)
        this.gender=gender
    }
    sayHello(): void {
        console.log('汪汪汪')
    }
}
let cat=new Cat('咪咪',3,1)
let dog=new Dog('旺旺',5)
dog.run()
console.log(cat)
  • 接口
    • 用来界说一个类中应该包含哪些特色和办法
    • 接口中的一切特色都不能实践值
    • 接口只界说方针的结构,而不考虑实践值
    • 在接口中的一切办法都是笼统办法
interface myInterfaceType {
    name: string
    age: number
}
const obj: myInterfaceType = {
    name: '遇萤',
    age: 22
}
console.log(obj)
interface newType {
    name: string;
    sayHello(): void
}
  • 特色封装:特色能够被恣意的修正导致方针的数据变得十分的不安全
    • 特色修饰符
      • private:经过private界说的特色能够经过在类中增加办法使得外部拜访到private特色
      • public
      • protected:受保护的特色,只能在其时类和其时类的子类中运用
    • getter和setter
      • get 特色(){return this.特色} set 特色(val:特色值类型){修正操作}
class MyClass implements newType {
    name: string;
    constructor(name: string) {
        this.name = name
    }
    sayHello(): void {
        console.log('qqqq')
    }
}
class Person{
    private _name:string;
    private _age:number;
    constructor(name:string,age:number){
        this._name=name
        this._age=age
    }
    sayHello(){
        console.log('hi~')
    }
    // 界说办法,用来获取name特色
    getName(){
        return this._name
    }
    setName(val:string){
        this._name=val
    }
}
let per1=new Person('遇萤',20)
  • 泛型
    • 在界说函数或是类时,假如遇到类型不明确就能够运用泛型
function fn<T>(a:T):T{
    return a
}
// 能够直接调用具有泛型的函数
let res=fn(10);   //不指定泛型,TS能够主动对类型进行推断
let res2=fn<string>('遇萤')  //指定泛型
// 泛型能够一同指定多个
function fn2<T,K>(a:T,b:K):T{
    console.log(b)
    return a
}
fn2<number,string>(20,'遇萤')
//约束泛型的规模 
interface Inter{
    length:number;
}
// T extends Inter 表明泛型T有必要是Inter完成类(子类)
function fn3<T extends Inter>(a:T):number{
    return a.length
}
console.log(fn3({length:123}))
class MyClass<T>{
    name:T;
    constructor(name:T){
        this.name=name
    }
}
const mc=new MyClass<string>('遇萤')

遇到的问题

1.typescript | 处理tsconfig.json提示“ts找不到大局类型Array、Boolean”、

报错原因:lib数组里的项存在依靠联系

处理方案:参加”esnext”项就好了。

{
  "compilerOptions": {
    "allowJs": true,
    "target": "es5",
    "lib": [
      "dom",
      "esnext",
      "es2015.promise"
    ]
  }
}

2.关于运用webpack热更新出现 cannot get/chrome.exe

原因不明

处理方案:

//原始写法
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack -- mode development",
    "start": "webpack serve --open chrome.exe"
  },
//正确写法 去掉chrome.exe
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "dev": "webpack -- mode development",
    "start": "webpack serve --open"
  },

上面记载的都是作者自学时记载的笔记,比较根底。因为之前学的是js,完全没触摸过ts,上面的内容仅仅协助自己先入个门,这里推荐咱们去看尚硅谷李立超教师讲的typescript,教师特别有梗!真的!!!!简单入门之后,必定要对所学常识进行检测。跟着李教师的课学,后边会讲一个实践(贪吃蛇小游戏)。

创立基于 TS 的 React 项目

实习最终一次查核的内容是建立一个后台办理体系,只需两个模块,数据模块和标签模块。数据模块包含增删改查以及分页五个功用,标签模块包含增删改查四个功用。

前端建立

1.项目初始化

npx create-react-app my-app --template typescript

2.相关依靠下载

# 库房相关插件下载
npm i redux @reduxjs/toolkit react-redux
# 路由相关
npm i react-router-dom
# 结构相关
npm i antd
# 恳求相关
npm i axios

别号装备

1.新建一个config-overrides.js文件,增加以下代码

const { override, addWebpackAlias } = require('customize-cra')
const path = require('path')
module.exports = override(
  addWebpackAlias({
    // 指定@符指向src目录
    '@': path.resolve(__dirname, 'src'),
  })
)

2.检查tsconfig.json文件中paths字段是否存在对应装备,如不存在需求加上

"paths":{
    "@/*":["./src/*"],
}

3.从头发动项目后就能够运用对应别号了

如何修正webpack装备

选择react结构进行前端开发时,基本上都会用到create-react-app这个官方供给的脚手架。但是这个脚手架有个很大的坏处,不能直接对该脚手架的默许选项进行装备。相比于vue最新的脚手架@vue/cli而言,@vue/cli尽管也对webpack的装备进行了全面的封装,但是官方答应用户在项目根目录创立一个vue.config.js进行装备,而且供给了丰厚的API,供用户去参阅。假如用户想装备create-react-app脚手架中的webpack进行便利开发的话,如同之后经过npm run eject这一条路走,网上大部分的教程也是这样的,即把默许装备悉数弹出进行操作。但是这种办法有两个缺陷:npm run eject命名不可逆,一旦装备文件暴露后就不可再躲藏;扩展的装备和create-react-app内建的webpack装备混合在了一同,不利于装备出现问题后的排查。 运用craco进行在装备 1.插件下载

npm i -D @craco/craco

2.在项目的根目录下新建craco.config.js文件,后续的webpack装备写在这里。

3.修正package.json里的发动装备

 "scripts": {
    "client": "craco start",
    "server": "node ../server/app",
    "start": "npm run client --mode dev",
    "build": "craco build --mode production",
    "test": "craco test",
    "eject": "react-scripts eject"
  }

装备署理

办法一:运用http-proxy-middleware

1.依靠下载

npm i -D http-proxy-middleware

2.在项目根目录下新建一个setupProxy.js文件,并增加装备信息

const {createProxyMiddleware}=require('http-proxy-middleware')
module.exports=function(app:any){
    app.use(
        createProxyMiddleware(
            '/api',//署理称号
            {
                target:'http://localhost:8081',
                changeOrigin:true,
                pathRewrite:{"^/api":" "}  //恳求中将/api替换成空
            }
        ),
    )
}

办法二:运用webpack-dev-server的proxy装备,在craco.config.js里增加装备信息。

const CracoLessPlugin = require("craco-less")
const path = require('path')
const pathResolve = pathUrl => path.join(__dirname, pathUrl)
module.exports = {
    //署理
    devServer: {
        proxy: {
            '/api': {
                target: 'http://localhost:3001',
                changeOrigin: true,
                pathRewrite: {
                    "^/api": ''
                }
            }
        },
        historyApiFallback:true,
    },
    babel: {
        plugins: [
            ['@babel/plugin-proposal-decorators', {
                legacy: true
            }]
        ]
    },
    plugins: [{
        plugin: CracoLessPlugin,
        options: {
            lessLoaderOptions: {
                lessOptions: {
                    modifyVars: {},
                    javascriptEnabled: true
                }
            }
        }
    }]
}

款式装备

npm i -D sass

新建对应的scss文件,采用less的格局书写款式,在需求运用款式的组件中引进scss文件

库房装备

一个有用的插件,可看到更新后的state Redux DevTools(直接在扩展商城搜)

1.store/index.ts

import { configureStore } from "@reduxjs/toolkit";
import logger from "redux-logger";
import { useDispatch, useSelector, TypedUseSelectorHook } from "react-redux";
import users from "./modules/users";
// 装备中间件
// RTk已经默许运用了redux-thunk,这里不需求额定引进了
// 假如需求一些自界说的中间件,能够经过调用getDefaultMiddleware
// 并将成果包含在回来的中间件数组中
// 案例中运用了日志的中间件,能够追寻到哪个页面在哪个时分运用了该reducer
// 而且能够显现调用前的数据状态和调用后的数据状态middleware: (getDefaultMiddleware) => getDefaultMiddleware({}).concat(logger),
export const store = configureStore({
  reducer: { users },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }).concat(logger),
  devTools: process.env.NODE_ENV !== "production",
});
// 大局界说 dispatch和state的类型,并导出
// 后边运用过程中直接从该文件中引进,而不需求冲react-redux包中引进
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

2.子库房装备

我这里是把子库房放置在store/modules文件夹下

store/modules/user.ts


import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
interface IUser {
    userName: String,
    userPass: String,
    userNick: String,
    userImg: String,
    userEmail: String,
    userPhone: String,
    _id: string,
    token: string
}
const initialState = {
    userName: 'admin',
    userPass: '20020915',
    userNick: '',
    userImg: '/img/a.jpg',
    userEmail: '',
    _id: '',
    userPhone: '',
    token: ''
} as IUser;
const counterSlice = createSlice({
    name:'logininit',  //库房称号
    initialState:initialState,
    reducers:{
        Init:(state:IUser,action:any)=>{
            console.log(action)
            state={...action.payload}
            return state
        },
    }
})
export const {Init}=counterSlice.actions
export default counterSlice.reducer

3.项目的进口文件index.tsx中挂载库房

4.在组件中运用dispatch和获取库房数据

# 引进操作办法
import { useAppDispatch, useAppSelector } from '../../store/index'
# 获取库房数据
const userMes = useAppSelector((state) => state.users);
# 操作库房
const dispatch = useAppDispatch()
dispatch({
       type: "datainit/Init",
       payload: [...res.all]
})

redux获取数据存在bug

当即将需求进行渲染的组件需求获取相应的数据时,首先想到的是经过路由传递数据或者经过redux获取数据。但这两种办法都存在数据丢掉的风险,关于路由传递数据,只需改写页面,数据就有或许丢掉(路由数据有必要由上一级传递到下一级才干成功获取)。关于redux,因为初始数据是咱们在登陆成功跳转前运用dispatch写入的,只需一改写,库房的state就会恢复到初始状态。这里供给一种种办法,经过路由中的loader装备项,完成改写就会从头获取而不会导致数据丢掉的功用。咱们只需求在对应路由组件中经过useLoaderData进行获取即可。其实也能够运用useRef对初始数据进行复制。

{
    path: 'person',
    element: <Suspense><Person /></Suspense>,
    loader: async () => {
        let str = localStorage.getItem('ECSDVEFT_SXFSC') as string
            return await getUser({ token: str })
    },
}

路由装备

因为路由装备与我之前js版别的装备没有差异,这里就不再记载。现在来说一下,在运用router时遇到的问题。

1.权限操控

在需求登录的体系中,假如未登录前不答应经过在地址栏输入对应路由地址然后完成跳转。在vue中能够经过路由护卫完成此约束,可在react中没有路由路由护卫这种说法。我检查了许多文档,最多的处理办法都是封装一个权限操控组件,然后完成路由权限操控。我看的头都大,又倒回去看v6版别路由的教程,在看到loader这个字段的时分,我就想是不是能够运用loader和redirect结合,然后完成此功用呢。我测验了一下,是能够的。事实证明,多看文档是会发现惊喜的。假如咱们还有更好的处理办法,能够share一下呀!咱们互相学习嘿嘿。

[router:文档链接](Home v6.15.0 | React Router)

这个是别的一个后台办理的项目的路由表

import { createBrowserRouter } from 'react-router-dom'
import React, { Suspense, useRef } from "react"
import Login from '@/views/Login/Login'
import NotFound from '@/component/NotFind/NotFind'
import { judgeToken } from '@/api/list'
import { redirect, useNavigate } from "react-router-dom";
import { message } from 'antd';
import { getUser } from '@/api/list'
const Home = React.lazy(() => import('@/views/Home/Home'))
const Person = React.lazy(() => import('@/views/Person/Person'))
// 未登录前不答应经过地址栏跳转至对应的页面
const loader = async () => {
    const user = localStorage.getItem('ECSDVEFT_SXFSC')
    if (!user) {
        console.log(user)
        message.warning('请先登录!')
        return redirect("/");
    }
    let str = localStorage.getItem('ECSDVEFT_SXFSC') as string
    return await getUser({ token: str })
};
export default createBrowserRouter([
    {
        path: '/',
        element: <Login />,
    },
    {
        path: '/home',
        loader: loader,
        element: <Suspense><Home /></Suspense>,
        children: [
            {
                path: 'person',
                element: <Suspense><Person /></Suspense>,
                loader: async () => {
                    let str = localStorage.getItem('ECSDVEFT_SXFSC') as string
                    return await getUser({ token: str })
                },
            },
            {
                // 不匹配时进入
                path: '*',
                element: <NotFound></NotFound>
            }
        ]
    },
    {
        // 不匹配时进入
        path: '/404',
        element: <NotFound></NotFound>
    }
])

2.按用户权限进行页面渲染

问题描绘:在后台办理这个体系中,有许多的用户,每个用户的权限都是不一样的。比方,当用户未超级办理员时,他具有最高权限,能够检查并操作体系中的任何数据。当用户为普通用户时,他只能进行对自己信息的修正以及一些上层创立他的办理员赋予他的权限,当他经过地址栏拜访了无权拜访的页面,要报404,而不是直接进入页面。

处理思路:我最开始仍是想用loader去处理,但对立这时产生了,假如是经过点击跳转到对应页面,那么经过window.location.pathname来获取其时途径获取的是跳转前的途径。但假如是经过在地址栏输入进行跳转,是能够运用loaderwindow.location.pathname结合进行约束的。因而,咱们还需求在组件中进行现在,在对应的跳转函数中进行用户权限的判别,没有权限则跳转到404组件。

// 没有权限不答应经过地址栏跳转至对应的页面
const judge = async () => {
    let str = localStorage.getItem('ECSDVEFT_SXFSC') as string
    let res = await getUser({ token: str })
    let currentPath = window.location.pathname
    let powerArr = res.data.loginRole.rolePower
    if (powerArr.includes(currentPath)) {
        //有权限
       return powerArr
    } else {
       // 无权限 跳转404
       return redirect("/404")
    }
};
//在操作函数中进行约束
// 顶部菜单跳转路由
const select = (item) => {
    //let powerArr=data.data.loginRole.rolePower
    if(data.includes(item.key)){
      navigate(item.key)
    }else{
      navigate('/404')
    }  
}

总结:其实我想完成的是,依据用户的权限去渲染组件,而不是把一切的功用都展现出来,点击跳转之后再去判别用户有没有权限,假如咱们有好的处理办法,能够共享出来咱们一同学习学习呀!

axios封装

项目运转npm run build打包之后,把build作为后端的静态文件夹资源,运用localhost:3001翻开项目后,页面报404跨域了!我还疑惑了好久,分明已经装备了署理,为什么还会跨域。咱们需求分清楚,署理是咱们装备在开发环境中的,在出产环境自然会失效。这是怎样办呢?我查找了许多文章,网上的处理办法首要是装备nginx,这玩意我配不明白,我就放弃了,选择别的一种办法。

处理办法: 在axios发送恳求前,先判别此刻是出产环境仍是开发环境

开发环境下,拜访的是/api/user/getdata,运用署理处理跨域问题

出产环境下,因为咱们运用koa-static将静态文件夹定位到打包后build文件夹,所以恳求的是由后端直接建议的因而,不存在跨域问题。此刻恳求的地址为:http://localhost:3001/user/getdata

const service: AxiosInstance = axios.create({
  timeout,
  baseURL:process.env.NODE_ENV=='development' ? '/api' : 'http://localhost:3001',
  headers: {
    Accept: 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
  },
  // 如需求带着cookie 该值需设为true
  withCredentials: true
});
//baseURL,增加公共前缀
export const GetData = (params: any) => request.get<any>('/user/getdata', params, { timeout: 15000 });

装备package.json

//打包的时分,声明此刻是出产环境
//npm start时,声明此刻是开发环境
 "start": "craco start --mode dev",
 "build": "craco build --mode production",

封装好的axios也共享给咱们

import axios from "axios";
import type { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios'
import { message } from 'antd';
// 根底URL,axios将会主动拼接在url前
let baseURL = process.env.NODE_ENV;
const getAuthToken = (token: string) => `Bearer ${token}`;
// 默许恳求超时时刻
const timeout = 30000;
// 创立axios实例
const service: AxiosInstance = axios.create({
  timeout,
  baseURL:process.env.NODE_ENV=='development' ? '/api' : 'http://localhost:3001',
  headers: {
    Accept: 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
  },
  // 如需求带着cookie 该值需设为true
  withCredentials: true
});
// 一致恳求拦截 可装备自界说headers 例如 languange、token等
service.interceptors.request.use(
  (config) => {
    // 装备自界说恳求头
    // let customHeaders: AxiosRequestHeaders = {
    //     language:'zh-cn',
    // };
    // config.headers = customHeaders;
    // let token = localStorage.getItem('ECSDVEFT_SXFSC')!
    // if(!token){
    //   config.headers['authorization'] = getAuthToken(token);
    // }
    let token = localStorage.getItem("ECSDVEFT_SXFSC")
    //token存在就赋值 不存在不履行
    token && (config.headers['Authorization'] = token)
    // console.log('token存在')
    //console.log(config);
    return config
  },
  (error: AxiosError) => {
    //console.log(error);
    Promise.reject(error)
  }
)
// axios 回来格局
interface axiosTypes<T> {
  data: T;
  status: number;
  statusText: string;
}
//核心处理代码 将回来一个promise 调用then将可获取响应的业务数据
const requestHandler = <T>(method: 'get' | 'post' | 'put' | 'delete', url: string, params: object = {}, config: AxiosRequestConfig = {}): Promise<T> => {
  let response: Promise<axiosTypes<any>>;
  switch (method) {
    case 'get':
      response = service.get(url, { params: { ...params }, ...config });
      break;
    case 'post':
      response = service.post(url, { ...params }, { ...config });
      break;
    case 'put':
      response = service.put(url, { ...params }, { ...config });
      break;
    case 'delete':
      response = service.delete(url, { params: { ...params }, ...config });
      break;
  }
  return new Promise<T>((resolve, reject) => {
    response.then(({ data }) => {
      //业务代码 可依据需求自行处理
      //console.log(data.errCode)
      if (data.errCode !== 0) {
        reject(data);
      } else {
        //数据恳求正确 运用resolve将成果回来
        //console.log(data)
        if ('showMsg' in data) {
          if (data.showMsg) {
            if (data.flag) {
              message.success(data.showMsg);
              //message.success(data.msg as string)
            } else if (!data.flag) {
              message.warning('' + data.showMsg)
            }
          }
        }
        //message.success(data.msg)
        resolve(data);
      }
    }).catch(error => {
      // let e = JSON.stringify(error);
      message.warning(`网络过错:${error}`);
      // console.log(`网络过错:${e}`)
      //console.log(`网络过错:${error}`)
      reject(error);
    })
  })
}
// 运用 request 一致调用,包含封装的get、post、put、delete等办法
const request = {
  get: <T>(url: string, params?: object, config?: AxiosRequestConfig) => requestHandler<T>('get', url, params, config),
  post: <T>(url: string, params?: object, config?: AxiosRequestConfig) => requestHandler<T>('post', url, params, config),
  put: <T>(url: string, params?: object, config?: AxiosRequestConfig) => requestHandler<T>('put', url, params, config),
  delete: <T>(url: string, params?: object, config?: AxiosRequestConfig) => requestHandler<T>('delete', url, params, config)
};
// 导出至外层,便利一致运用
export { request };

后端建立

之前学习node.js时,运用的是express结构调配mongoose运用,实习阶段,公司要求咱们运用koa来建立接口,运用lowdb来存储数据。

依靠下载:

  • koa
  • koa-static
  • koa-router
  • koa-bodyparser
  • lowdb
  • koa2-connect-history-api-fallback

koa-static装备 运用办法很简单,只需求在进口文件装备以下句子就ok啦~

app.use(static('../client/build'))

koa-router和lowdb装备 1.新建route文件夹 2.新建父路由文件index.js,装备以下信息

const Router = require('koa-router');
const user = require('./user');
const router = new Router();
// 指定一个url匹配
router.get('/', async (ctx) => {
    ctx.type = 'html';
    ctx.body = '<h1>hello world!</h1>';
})
router.use('/user', user.routes(), user.allowedMethods());
module.exports = router;

3.新建子路由文件user.js

const Router = require('koa-router');
const router = new Router();
const {getTime}=require('../handler/options')
// const {db}=require('../handler/db')
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync'); // 有多种适配器可选择
const adapter = new FileSync('../server/db.json'); // 申明一个适配器
const db = low(adapter);
const {RangeTime,IncludeTabs,IncludeTime,Used,UsedTabs} =require('../handler/options')
const dayjs=require('dayjs')
router.get("/", async (ctx) => {
    console.log('查询参数', ctx.query);
    ctx.body = '获取用户列表';
})
.get("/getdata", async (ctx) => {
})
.post("/language", async (ctx) => {
})
.get("/getlanguage",async(ctx)=>{
})

koa-bodyparser装备 若想获取post恳求的带着的参数,还需求装备koa-bodyparser 在进口文件中增加一下装备信息

app.use(bodyPramas());
app.use(router.routes());
app.use(router.allowedMethods());

总结:后端运用了好几个中间件,运用的时分必定要注意他们的运用次序,像koa2-connect-history-api-fallback就有必要放置在静态文件夹之前,才干处理咱们的问题,至于koa2-connect-history-api-fallback效果是什么,后文会解说的。

在上面一切的装备完结之后,只需咱们的项目书写结束,打包之后,运转后端,再去拜访后端地址是能够成功拜访到咱们的项目的,但是,这时又冒出来一个bug,哦豁,只需页面改写,就会报404。这又是为何呢?这里就不得不提一下history和hash两种路由形式的差异啦。

history和hash的差异

hash:即地址栏URL中的#符号。比方这个URL:http://www.abc.com/#/hellohash的值为#/hello。它的特色在于:hash尽管出现在URL中,但不会被包含在HTTP恳求中,对后端完全没有影响,因而改动hash不会从头加载页面。

history:运用了HTML5 History Interface 中新增的pushState()replaceState()办法(需求特定浏览器支持)。这两个办法应用于浏览器的历史记载栈,在其时已有的back、forward、go的根底之上,它们供给了对历史记载进行修正的功用。仅仅当它们履行修正时,尽管改动了其时的 URL, 但浏览器不会当即向后端发送恳求。

history形式开发的SPA项目,需求服务器端做额定的装备,否则会出现改写白屏(链接共享失效)。原因是页面改写时,浏览器会向服务器真的发出对这个地址的恳求,而这个文件资源又不存在,所以就报404。处理办法就由后端做一个保底映射:一切的恳求悉数拦截到index.html上。

处理办法:

办法一:运用hash形式的路由(经测验,有效)

办法二:装备nginx(但这种办法如同不收效,这个nginx tmmd一直配不明白)

server {
        listen       80;
        server_name  localhost;
        location / {
            root html;
            index index.html;
            try_files $uri $uri/ /index.html;
       }
}

办法三:后端进行处理,运用koa2-connect-history-api-fallback 中间件

1.插件下载

npm i -D koa2-connect-history-api-fallback

2.在进口文件中进行装备

const { historyApiFallback } = require('koa2-connect-history-api-fallback');
const app = new Koa(); // 创立koa应用
const static=require('koa-static')   //装备静态文件夹
// koa2-connect-history-api-fallback中间件必定要放在静态资源服务中间件前面加载
app.use(historyApiFallback({index: '/index.html'}))
app.use(static('../client/build'))

总结

到这里,一切的内容就记载完了。文章通篇都仍是在讲述怎样装备各种插件和东西怎样装备怎样运用,共享出来,除了为了回顾并记载自己这一个半月的学习过程,还有一个目的是期望看到我文章的宝子在遇到相同的坑时,不至于挠破头皮也找不到处理办法。处理bug的过程是苦楚的,有时分一天下来就处理了一个bug,不要觉得这时效率不高的体现!真的便是在报错中前进呀!!!