前端学习纪要

一、HTML和CSS

1、html零碎知识点

1)vscode装插件引入其它软件快捷键(eg:idea)

在这里插入图片描述

2)html基本骨架

在这里插入图片描述

3)快捷键!+tab,直接生成html文件骨架

快捷键直接生成的html骨架,html标签的lang属性起初是en,表示英文,一般是要改成zh中文的,因为如果是en很多浏览器会比较“轴”,你在页面上写个中午,它就会弹框给翻译成英文,有时候很不需要。
或者,直接将lang属性去掉,因为这个属性默认就是zn。
在这里插入图片描述

4)快捷键,只写标签的名字+tab,就会直接得到一个标签

5)有序列表/无序列表

常见的ul标签、不常见的ol标签,其实分别是:
无序列表/有序列表
在这里插入图片描述

6)列表标签反复嵌套形成级联效果

如下图,将列表标签ul和行标签li反复嵌套,就会形成级联列表的效果
在这里插入图片描述

7)anchor本意“锚”,html的a标签,多叫超链接

通过超链接标签,可以分别跳转到

A、本地页面
B、互联网页面
C、本页面的其它锚点(比如底部、顶部)
在这里插入图片描述
在这里插入图片描述

以#开头的地址,不会跳出本页面

8)image标签

img标签用来在页面展示图片,其src属性有3种路径格式:
一是图片的路径;
二是data Url,可以把图片直接嵌在页面上,其格式如下

data:媒体类型;base64,数据

在这里插入图片描述

三是object Url,这种展示方式,需要用到js代码,暂时没法演示、后面学到时再用

9)video标签

video标签和image标签类似,都是通过src指定文件的地址;

但是如果只写src的话,视频没有播放等按钮,像个图片;
video有很多属性,最需要知道的就是controls属性,可以给视频加上开关、音量、进度条,非常实用。

10)audio标签

audio标签和image、video标签类似,都是通过src指定文件的地址;

但是如果只写src的话,音频没有播放等按钮,在页面上根本看不见;
audio有很多属性,最需要知道的就是controls属性,可以给音频加上开关、音量、进度条,非常实用。

在这里插入图片描述

2、html库

搜索mdn,进入MDN Web Docs这个网站;
选择reference-html,就可以看到种类繁多、说明详细的html标签介绍了。

3、css(层叠样式表)零碎知识点

整体流程就是,通过选择器定位元素,然后编辑元素的属性和值;最后调整布局。
推荐单独建一个文件,来放css配置

0)css的引入

首推:css放在一个单独的.css文件中,在使用的页面通过link标签引入;
在这里插入图片描述

不推荐:也可以在页面,把样式写在style标签中;

在这里插入图片描述

1)选择器

type选择器

class选择器

id选择器

type就是标签类型,

tips01:class和id的区别就是:class的值是可以重复的,id的值需要是唯一的

当然,如果你硬要id重复,浏览器会选择第一个来操作。

tips02:元素被多个选择器选中,终止效果如何?

选择器有优先级,id选择器 》 class选择器 》 type选择器

2)属性和值

属性有非常多:
比如,背景色background-color

比如,display
等等

不知道的,也可以去库里边看,比如mdn

3)布局

主要知道页面每个部位的名称,方便与其他程序员交流
在这里插入图片描述
与布局相关的html标签:
div 也叫容器标签

templa 标签

二、JavaScript

1、在浏览器控制台,可以通过js代码直接对页面元素进行操作

比如,查询某个元素的位置:

打document对象,调用getElementById()方法,回车即可查出对应元素,鼠标悬停在查询结果,页面会显示该元素。
在这里插入图片描述

再比如,可以为某个标签赋新值,以查看其效果

先定义一个a变量,指向元素;
再通过元素对象的方法改变其值、看效果。
(ps:这不就是java代码吗?)
在这里插入图片描述

注意:上述改动,只是临时的,刷新页面改动就消失了

缓存刷新

如上面用let定义了一个a变量,如果再用var定义一个a变量,就会由于重名而报错;

使用clear按钮,只能清除命令,不能清除缓存
在这里插入图片描述
那么这种场景下,该如何清楚缓存呢?
答,刷新以下页面就可以了

2、js的引入

方式一:在body标签中,写一个script标签,在这个标签中写js代码

方式二:把js代码放在一个专门的js文件中,然后在页面通过script标签的src引入

方式三:到了使用框架的阶段,上述两种引入方式用的就比较少了,到时候会有专门的引入方式

3、js的变量声明

js的变量有let、var、const三种,每种变量类型都可以赋值任意数据类型,这点与java不同

1)let a = 0;
let a = ‘a’;
let a = [1, 2, 3];

let可以被多次赋值,其最终值就是最后一次赋的值

2)var b = 1;
var b = ‘b’;
var b = [1, 2, 3];

var也可以被多次赋值;
关于var和let的区别,后面讲到作用域时,老师会再详细讲;
这里只要记住:能用let的地方就尽量不用var,因为var容易出现作用域的问题。

3)const c = 1;
const c = ‘c’;
const c = [1, 2, 3];

const表示常量,它不能被重复赋值,如上面三行针对变量c的赋值,只有第一行能成功,后面两行都会失败并报错;
const和java中用finnal修饰的变量非常相似。
在这里插入图片描述
不过要注意,const不能被重复赋值,但它如果表示对象的话,对象的值是可以改变的。
什么意思呢?
比如 const c = [1, 2, 3];
c[2] = 4;是可以被执行的,
通过console.log©方法,可以打印其结果,可以看到c[2]确实被成功修改成4
(console.log方法,类似与java中的system.print,就是做控制台打印用的)
在这里插入图片描述

4、js的数据类型

数据类型1、2:undefinded和null

  • 执行表达式或函数,没有结果,就会出现undefinded
  • 访问数组不存在的元素,访问对象没有的字段,就会出现undefined
  • 定义变量没有初始化,就会出现undefinded

二者的共同点
a、都没有属性、方法;
在这里插入图片描述
在这里插入图片描述

b、Nullish ??

二者的区别
a、undefined由js产生;
b、null由程序员产生

数据类型3:字符串String

js中有3种字符串的写法:

# 1、双引号
let s1 = "abc";

# 2、单引号
let s2 = 'abc';

# 3、反引号
let s3 = `abc`;

支持多种写法,就可以更好的应对不同场景

eg1:分别用java、js表示字符串 <a href="www.abc.com">超链接</a>

# 对于java,jdk15以前,需要用到转义字符,否则字符串本身的双引号会和表示字符串的双引号混淆
String s = "<a href=\"www.abc.com\">超链接</a>"

# js因为支持单引号,就可以很好的规避这个问题;当然,也可以用反引号
let s = '<a href="www.abc.com">超链接</a>'

eg2:动态拼接请求参数 xxx?name=zs&age=18

#	法一:用传统的字符串拼接方式
let name = ;
let age = ;
let uri = "xxx?name=" + name + "&age=" + age;

#	法二:模板字符串的方式;这种方式必须用反引号
let name = ;
let age = ;
let uri = `xxx?name=${name}&age=${age}`;

补充:这里记录一个使用浏览器做前端测试的方法,先用变量把name、age、uri表示出来,该赋值的赋值,然后用console.log()把uri的结果打印出来
在这里插入图片描述

数据类型4、5:number和bigint

1)number在js中是按照双精度的浮点数去计算的;而且可以除以0
在这里插入图片描述
Infinity是正无穷大的意思,这个符号要了解
在这里插入图片描述
还有负无穷大 -Infinity
2)number在计算中有精度问题
在这里插入图片描述
例如,2.0 - 1.1结果不是0.9,而是如上图

3)数据类型转换:parseInt() 方法
parseInt() 字符串 -> 整数数字
注意,parseInt虽然看起来是将字符串转换成整型,但实际上它得到的结果仍然是一个number类型的数字,只是简写为整数了、省略了小数点后面的数字
在这里插入图片描述
如果使用parseInt转换失败,会得到一个特殊值NaN,它是not a number的缩写
在这里插入图片描述
这个特殊值也要知道,以后遇见了这个报错,就要想到代码中某处作类型转换,入参是不能转换的字符串导致了报错

4)数据类型转换:用减法(隐式转换)
在这里插入图片描述
如图,想要将字符串转换为数字,可以直接减去数字0,得到到的结果就是数字;
js会自动作类型转换。

类似的,用加法、加0,应该也能触发隐式转换。

5)在数字后面加一个小n,即可标识该值为整型bigint,而不是number
在这里插入图片描述
如上图,整数是不能除以0的

数据类型6:boolean

1)在js中,并不是只有boolean才能用作条件判断
在这里插入图片描述
在js中,那些在条件判断时被当成true的值,归类为Truthy;
那些在条件判断时被当成false的值,归类为Falsy

2)Falsy

  • false
  • Nullish(null、undefined)
  • 0、0n、NaN
  • “”、‘’、``
    这里要注意,

3)truthy
除了falsy,剩下的绝大多数都是truthy

4)易错点
字符串中,只有空串才被视为Falsy,
像"0"、"false"等,都是非空字符串,都属于Truthy!

另外,后面要学的空数组、空对象,都属于Truthy!
[]、{}

数据类型7:对象类型1:Function

在这里插入图片描述
1)js中的函数,其参数列表不需要像java一样定义参数类型

在这里插入图片描述
2)js中参数个数,可以不按照函数定义的个数传;比如定义两个参数,但是你传了3个参数,它也不会报语法错误,只是第三个参数不会被用到
在这里插入图片描述

同理,如果少传参数,也不会报语法错误,比如
在这里插入图片描述
要求两个参数,只传一个参数,另一个相当于undefined;
而一个数字 + undefined,其结果就是NaN。

3)默认参数

在这里插入图片描述
以上就是js函数默认参数设置的方法,非常简单;
有了默认参数的设置后,函数可以传全部参数、也可以传部分参数、也可以不传参数;缺少的部分,会自动启用默认参数值
在这里插入图片描述
补充,如上面的例子,如何做到只给后面的size传值,而不给page传值;
好像没法做到不给page传值、而直接给size传值;但是可以通过给page赋undefined,从而实现让page使用默认值、size使用传入的值
在这里插入图片描述

4)匿名函数

匿名函数就是没有函数名的函数;一般定义匿名函数的时候,要在外边加一对圆括号,将内部的匿名函数隔离开、让其始终为一个整体
在这里插入图片描述
匿名函数使用场景一:定义完后立刻使用,而且不会在其他地方调用了(类似于java的匿名内部类)
在这里插入图片描述
如上图,(1, 2)就是在调用刚才定义的匿名函数,其运行效果如下
在这里插入图片描述
匿名函数使用场景二:作为其它对象的方法

例如,作为触发事件的执行函数
在这里插入图片描述
用匿名函数实现上述效果,就是:
在这里插入图片描述

5)箭头函数

在这里插入图片描述

  • 如果只有一个参数,()可以省略
    注意:只有一个参数才可以省略,没有参数、多个参数都不能省略

  • 如果函数体内只有一行代码,{}可以省略

6)js中的函数是一个对象

以下函数用法在js中非常常见
1、赋值(上面的例子有看到匿名函数、箭头函数参与赋值,具名函数也能参与赋值)
在这里插入图片描述
2、函数和对象一样,有属性、有方法
console.log只能打印函数的定义,看不到函数的结构;
在这里插入图片描述
可以使用console.dir()方法,查看函数的结构
在这里插入图片描述

  • 其中带有f标记的是方法,不带的是属性
  • 带有->符号的可以继续展开,浏览器限于篇幅给省略了;
  • 带有[[ ]]的是内置属性,不能访问,只能查看
  • 比较重要的内置属性是[[prototype]]和[[scopes]],会在后面学继承和作用域时学习

3、函数可以作为方法参数
在这里插入图片描述

4、函数可以作为方法的返回值
在这里插入图片描述
如上图,在方法c中定义了一个函数d,并将函数d作为返回值;
那么c()就是调用函数c,会返回一个函数d;
怎么调用函数d呢?
就是在返回值后面再写(),就是调用返回的函数d,即c()()。

如上图测试,先调用函数c,打印了c;
再调用函数d,打印了d。

补充:再js中,会把

  • 以函数为参数的函数,如上面的函数b
  • 以函数为返回值的函数,如上门吗的函数c

称之为"高阶函数"

7)函数的作用域

在这里插入图片描述
看下面的例子
在这里插入图片描述
这里与java类似,在a函数内部,可以访问x变量,在b函数内部也可以访问y变量;但是不能访问c函数内的变量c;

注意:console.log(x, y),它的打印是 10 20,中间没有逗号。

反例:
在这里插入图片描述
报错,就是因为在函数a中使用了变量z,明显是不能使用的
在这里插入图片描述

总结:js函数的作用域,与java的局部变量、全局变量基本一样,就是在什么位置、可以使用那些变量,作为老java开发很好理解

8)闭包

在这里插入图片描述
如上图,a()函数调用得到了b()函数,在外面执行b函数a()(),能否正常的打印x、y?
答案是:可以
因为不论在哪里调用b函数,b函数的作用域在其定义的时候就已经确定好了

  • 而所谓的"闭包",就是指"函数能访问自己作用域中的对象",即使它被作为返回值传到了别的地方,它也能访问自己作用域中的变量。

补充:java中没有闭包的概念,但是感觉工具类的静态方法,就能实现类似的效果。

填坑:数据类型var与let作用域的区别

1、let变量

let变量,会把普通{}也作为作用域边界;普通{} = 非函数{}
在这里插入图片描述
上面截图中js代码,打印b函数的作用域,一共有3个分别是:
(注:console.dir(b)打印b函数的作用域,b函数内部不算在作用域中,只算函数外面的,因为内部是函数本身、而不是它的作用域;这里我们主要了解var、let变量作用域的区别,要从变量的角度看作用域,不需要纠结函数内部算不算函数作用域这个点)

2、var变量

如果是var变量,普通{}不会视为边界;函数{},才会被视为边界。
在这里插入图片描述
上图中,b函数只有一个作用域

3、
在这里插入图片描述
如上图,使用var,所以if{}内外的变量e处于同一个作用域之中;
在同一个作用域中的变量不能同名,只是js对于这种情况做了处理、它会把if{}内的var e = 20视为赋值,而不是定义;
所以不会报错,但是上面代码相当于只有一个变量e
这样e的值由10改成了20。
所以,最终if{}内外的console.log(e)打印的是同一个变量的值,都是20。

同样的代码,将变量e的类型由var改为let;
在这里插入图片描述
这时if{}内外对let e来说是两个作用域,if{}内外的console.log(e),打印的是两个变量的值,它们的值不同。

4、老师建议我们多用let变量,因为它和java变量的作用域规则相同,这样我们就不会因为作用域遇到各种奇怪的问题。

早起js并没有let这个变量类型,如果对于代码
在这里插入图片描述
就是想要{}内外的两个console.log打印一样,怎么办呢?
答案是,用函数包一层、把内外两个var e分开。
在这里插入图片描述

数据类型8:对象类型:Array

1)语法

在这里插入图片描述
在这里插入图片描述

2)常用方法

1、追加元素push()
arr.push(4);
数据arr就变成了[1, 2, 3, 4]

2、删除元素
法一:用shift()方法
arr.shift(),从数组左侧移走一个元素,arr变成[2, 3, 4]

法二:用splice()方法
arr.splice(index, 删几个元素)
例如 数组let arr = [1, 2, 3, 4];
arr(1, 1),数组就变成[1, 3, 4]
再执行arr(1, 2),数组就变成[1]

3、连接(把多个元素连成一个字符串)
let arr = [‘a’, ‘b’, ‘c’]
arr.join() 不传参数,默认用逗号作为分隔符,结果为"a,b,c"
arr.join(’ '),用空格作分隔符,结果为"a b c"

4、映射方法map()
map的参数是一个方法,map会把数组的每个元素作为入参、执行入参方法,用执行完的结果,构建一个新的数组返回;
例如:
function funA(i) {
return i * 10
}
let arr = [1, 2, 3]
arr.map(funA)
数组就变成了[10, 20, 30]

map()的进阶用法(实际常用)

因为funA可能只会用一次,如果专门写个函数有点浪费,可以和java的lambda表达式一样,用以下写法

arr.map(i => i * 10)

这样的效果和上面一样。

补充:实际上不只针对map,任何以函数为入参的方法,都可以通过上面类似lambda表达式的写法进行简化。
具体的省略方式:

1)首先是函数可以简化为匿名函数,也叫箭头函数;(i) =>{return i * 10}
2)只有一个参数时,参数列表的()可以省略;i =>{return i * 10}
3)只有一行代码,{}可以省略;i => return i * 10
4)只有一行代码,return也可以省略;i => i * 10

5、筛选filter()
入参是函数,返回一个数组;filter会筛选出满足入参函数条件的元素
在这里插入图片描述
7、遍历forEach()
在这里插入图片描述
注意:filter和forEach都不会改变数组;但是map会改变数组的元素值
在这里插入图片描述

高阶函数、回调函数的定义

如果一个函数fun接收另外一个函数method作为参数,或者它的返回值是另外一个函数,那么就可以把这个函数fun叫做高阶函数;
上面说的map(), filter(), forEach()都是高阶函数;
而作为高阶函数参数的函数method,则可称之为回调函数。

数据类型9:对象类型:Object

1)基础

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例2的写法,就是先在外边定义属性、方法,然后定义对象把刚才的属性、方法聚合到对象里边;例2的效果和例1是相同的
在这里插入图片描述
这种写法,对对象的函数进行了简化;但是这种写法的函数,只能在对象内用,在对象外用会报语法错误。

在这里插入图片描述
// js中约定,前面加了_的属性,不要直接访问,而是要通过set、get方法访问;当然这是一种约定,如果强行直接访问,也是能访问到的。
在这里插入图片描述
在js中,对象中的set、get方法不用直接调用;

stu4.name = “小白”;实际上就已经调用了set方法对name进行了赋值;

console.log(stu4.name);实际上就已经调用了get方法来获取name的值了。

2)js对象特点1:对象的属性、方法可以增删

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3)js对象特点2:this关键字

js中的this关键字与java一样,也是隐式参数;但是它具体指代的是什么与函数运行时的上下文有关。

情况一:在一个“落单的”函数中,this指代的是全局对象window
(落单函数,就是指不是某个对象直接下辖的函数,不太清楚它的“归属”,这种函数)
window对象的name属性本身是空串,但是我们也可以给它赋值,来更明显的观察
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
情况二:同样是上面的study方法,但是当它在某个对象内部时,this所指代的,就是当前对象了
在这里插入图片描述
情况三:通过call()方法,动态的改变this所指代的对象
call()方法有两个参数,第一个是this指代的对象,第二个是调用函数的入参
在这里插入图片描述
情况四(特殊情况):在箭头函数内出现的this,以外层函数理解
在这里插入图片描述

第一个this,处于对象的函数内部,其指代的是stu对象;
第二个this,它不直接属于某个对象、不明确归属,即处于一个“落单”的函数中;
由情况一可知,落单函数中的this指代的就是windo对象;
根据以上分析,第二个this指代window对象,window对象的name属性初始值为空,即this.name为空,所以其运行结果为
在这里插入图片描述
上面的js代码其实并不符合编写的初衷,初衷是第二个this也要指代stu对象而不是windows对象;那么如何实现这一目标呢?
可以通过情况四说的,在箭头函数内的this,要视为其是外层函数中的this,即就当箭头函数不存在;
所以代码改成如下格式,第二个this虽然在箭头函数内,但是视为它是play函数中的this、仿佛箭头函数不存在,这样就实现了本段代码设计的初衷了。
在这里插入图片描述
在这里插入图片描述

补充:在箭头函数出现前,如何实现上述目标呢?
就是在play函数中,先设置也给对象类型的变量,接收this,这时这个变量就是play()中的this、也就指代的是stu对象了;
然后再把这个变量作为参数传入匿名函数,这样代码就不涉及第二个this的问题了、没有第二个this了,不用烧脑了,它就是一个指代当前对象的变量。
在这里插入图片描述
在这里插入图片描述
但是有了箭头函数,就要尽量用箭头函数;难理解的部分,多看几遍就懂了。

4)原型继承(原型继承,就是js中的“对象继承”)

一、基础知识
在js中,继承是发生在对象之间的;
有继承关系的两个对象,实际上就是将子类对象内的_porto_属性指向父类对象;
proto属性是每个对象都有的一个属性,
当没有继承关系的时候,它指向的是Object、一个公共对象;
当有继承关系的时候,它指向自己的父类对象。
在这里插入图片描述

二、继承关系确立后,子对象调用属性、方法,会优先从自己这里找,如果没有就会自动通过_proto_去父对象找(这个过程是隐式的)

继承关系的对象,子类允许再添加自定义的属性、方法;
当子类有与父类同名的属性、方法时,会优先调用自己重写的属性、方法。

js中有“原型链”的概念,
就是子对象调用属性、方法,会优先从自己这里找,如果没有就会自动通过_proto_去父对象找,直到找到 or 到达Object(没有父类的对象的_proto_指向Object)。

可以通过下图表述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、在js中创建继承关系有很多方法
比如,通过Object.create()方法;
在这里插入图片描述
在这里插入图片描述
如上图,通过Object.create()方法,在其中传入父类对象,就可以得到一个继承了父类对象的子类对象;

四、补充:prototype意思是“原型”,就是上面说的_proto_,用来存父类对象,它在不同浏览器中显示不同。
在这里插入图片描述

5)基于函数的原型继承

一、基础知识
除了使用Object.create()来建立原型继承,js还通过了通过函数建立原型继承的方式;
这种方式的好处是,它可以在建立继承关系的同时,定义子类的属性、方法。
在这里插入图片描述
二、建立方式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如上面截图一,
先定义一个函数,其入参用来给子对象赋值;
在函数中通过this给子对象定义属性、同时赋值,也是通过this给子对象定义方法;

再如截图二,
在函数外,通过 方法名.prototype,给子对象建立父对象(以及两者的继承关系);
再通过 方法名.prototype,给父对象定义属性、同时赋值,也是通过 方法名.prototype 给父对象定义方法;

上述完成后还没有正的建立子对象、父对象、继承关系,只是做好了准备;
如截图三,
当使用new 方法名()代码后,才算真正的按上面的设定,建立了子对象、父对象,以及两者的继承关系

JSON与js对象的关系

json数据格式,与js对象格式非常像
在这里插入图片描述
在这里插入图片描述

不同点

但是它们是不同的,不能将其混淆;下面总结了它们的不同点:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

互相转换

在这里插入图片描述
上面两个转换方法了解就是,在实际开发中一般不会直接使用,它们都被封装在一些工具内部了。

动态类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ps:正因js动态类型导致的维护困难问题,后来有了TypeScript即ts,它就是要求限定变量类型的。

5、js的运算符和表达式

1)基础运算符 + - * / % **
前面加减乘除取余,和java一样;
最后的**,表示乘方
在这里插入图片描述
2)增强运算符 += -= *= /= %= **=
以+=为例,结果等于变量初始值 + 右边值;

3)位运算、移位运算,前端一般用不着,不说了

4)比较运算符:== != > >= < <=

5)严格相等运算符 ===,作逻辑判等

因为在js中,1 == 1返回true;1 == '1'也返回true;有时就会不满足需求;
而使用 1 === '1’会直接返回false;
即用===只有当类型相同时,才进行判断,类型不同直接返回false

同理 !== 严格不等运算符也是类似的。

补充:typeof运算符,查看某个值的类型
在这里插入图片描述

6)js的逻辑||不同于java的应用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

7)?? 与 ?.

在这里插入图片描述
在这里插入图片描述
??如果左边是nullish,就返回左边的值,如果左边不是nullish,就返回??右边的值。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是上面的访问方式有问题,当返回对象中的address不存在或为null时,如上面的stu2、stu3,再访问address就会报错
在这里插入图片描述
在这里插入图片描述

8)展开运算符 ...

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
如上图,并没有修改的o2的address.city的值,但是o2的address.city的值会随o1的address.city值变化而变化。
说明展开运算符复制的是address的地址,而不是值。

在这里插入图片描述

在这里插入图片描述
合并时要注意顺序,后面的属性会覆盖前面的同名属性
在这里插入图片描述

9)解构赋值 运算符 [] {}

一、用在声明变量时
需求:有一个数组变量 let arr = [1, 2, 3]
现在想把数组的3个元素分别赋值给a、b、c三个变量。

let a, b, c = arr可以吗?
不可以,这样操作,a、b都是undefined,c=[1, 2, 3],相当于把arr赋值给了c;

这时就可以用解构赋值

let [a, b, c] = arr;	//	结果 a=1,b=2,c=3

二、用在声明参数时
需求:有一个数组变量 let arr = [1, 2, 3]
现在想把数组的3个元素分别赋值给方法test()的3个入参a、b、c。

对于function test(a, b, c),直接赋值test (arr)可以吗?
不可以,这样操作,b、c都是undefined,a=[1, 2, 3],相当于把arr赋值给了a;

这种情况最好也用解构赋值

function test([a, b, c]) {
	。。。
}
test(arr);
//	这样a、b、c三个入参的值正好就是数组arr的3个元素

注意:数组展开运算符 和 解构赋值,都能实现上面需求,它们有什么区别?

  • 一是,实现形式不一样,解构赋值是在函数定义的时候操作,调用的时候没区别
    而展开运算符在函数定义的时候没区别,在调用的时候操作。

  • 二是,展开运算符,只能实现第二个需求,不能实现第一个需求

三、对象解构,用在声明变量时
在这里插入图片描述
四、对象解构,用在声明参数时
在这里插入图片描述

6、js的控制语句

在这里插入图片描述

1)for in

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面截图的.语法无法获取属性值,因为这里n是变量,不能用.语法获取变量值

在这里插入图片描述
这里需要用[]语法获取变量值
在这里插入图片描述

2)for of

在这里插入图片描述

在这里插入图片描述
for of 配合 解构运算符
在这里插入图片描述

3)try catch

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
补充,js的try catch语法也支持finally,和java一样,无论是否异常,finallly都会执行

7、js的api

1)搭建前端服务器express
第一步、在指定目录打开cmd,输入以下命令初始化(下载、初始化)

npm install express -save-dev

# 也有同学说,输入以下命令,可以达到相同的效果
npm init -y

第二步、编写启动express的代码
新建main.js,并写入如下代码

import express from 'express'
const app = express()
app.listen(7070)

然后在终端输入node main.js命令,就可以在浏览器、通过express前端服务器访问项目资源了
当前因为项目没有静态资源文件,效果不明显。

第三步,为了方便看效果,添加静态资源
先在main.js中添加静态资源读取位置,如下图,./代表当前文件所在目录
在这里插入图片描述
在当前目录新建一个index.html静态资源,即可在页面访问。

2)查找页面元素

2)查找页面元素

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 需求一:查询某个页面元素

方式一:在控制台用选择器api,即querySelector()查询,下图是用类选择器, .类名
在这里插入图片描述

  • 需求二:querySelector()这个api,若查出多个结果,只会返回第一个元素,现在要求查出所有满足的元素。

方式二:使用querySelectorAll()这个api,会查询所有元素,多个结果时返回列表
在这里插入图片描述

  • 需求三:只查表头中的4个class="col"的元素,不能使用querySelector()、因为它只能返回第一个符合的元素;也不能使用querySelectorAll()、因为它会返回所有符合的元素,会返回12个结果,我们只要前4个。

方式三:链式调用api,先用querySelector(‘.thead’)把范围确定在表头这个div中,再用querySelectorAll(‘.col’)选中表头中的col
在这里插入图片描述

  • 需求四:通过元素id,定位元素

方式四:对于有id的元素,除了能用querySelector()外,还可以用getElementById()这个api
在这里插入图片描述

3)修改元素内容

在这里插入图片描述
在这里插入图片描述

  • innerHTML与textContent两个api的区别
    在这里插入图片描述

如下图,innerHTML会对赋值内容、当作html标签来处理
在这里插入图片描述
在这里插入图片描述
而textContent,不会把赋值内容当作html标签来处理,只会把它当作文本
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4)利用模板

需求,通过模板给table循环赋值,值来自后端返回的列表(这里先用array变量模拟后端返回结果);
先将原本table的标签用一个空标签代替
在这里插入图片描述
再写一个模板标签,模板标签一般用来存会重复使用的内容,比如本例中的列表行展示内容
在这里插入图片描述

用如下js代码实现上述需求:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5)用api fetch(url, options)-同步接收结果

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 使用示例
    注意:如下图所示,fetch获取的resp,从resp中通过.json()提取的数据也是promise,也需要用一个await关键字同步的获取列表数据。
    另外,这里有一个不明白的点,为什么函数前面要加一个async关键字?
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

为了使代码更加健壮,还应考虑返回结果报错的请况,与后端类似,可以使用try catch语句块捕获异常、做处理

async function findStudents() {
	try{
		。。。
	} catch(e) {
		console.log(e)
	}
}

6)用api fetch(url, options)-异步接收结果

如下图,异步调用,要通过then()从promise对象中获取结果;
而且与同步不一样的是,获取结果后要执行的代码,要放到匿名函数内部。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

为了使代码更加健壮,还应考虑返回结果报错的请况;在异步接收的情况下,可以对fetch结果继续进行链式操作.cathc(e)

fetch('students.json')
	.then( resp => resp.json() )
	.then( array => {
		......
	})
	.catch(e) {
		console.log(e)
	}

// 或者
fetch('students.json')
	.then( resp => resp.json() )
	.then( array => {
		......
	})
	.catch( e => console.log(e) }

7)跨域-请求响应头解决

fetch-不同源-拒绝
在这里插入图片描述
正常不同源的响应头
在这里插入图片描述
解决思路:将tomcat响应头修改为谁都可以用的
在这里插入图片描述
浏览器实例
fetch发起的请求,请求头里包含
在这里插入图片描述
但是后端不做处理的情况下,响应是不会包含 Access-Control-Allow-Origin的,
在这里插入图片描述
这种情况下,浏览器无法识别响应与请求发起者是否同源,故而报错

Access to fetch at .... blocked by CROS policy:No 'Access-Control-Allow-Origin' ....

可以通过注解:

@CrossOrigin("http://localhost:7070")

为在响应头中设置Access-Control-Allow-Origin,注解中设置的属性,就是Access-Control-Allow-Origin的值;
当设置为*时,表示后端服务器允许所有请求获取当前接口的返回结果。
在这里插入图片描述
这样,当fetch的请求头的Origin与响应头中的Access-Control-Allow-Origin,两者值相同时,表示服务器允许http://localhost:7070使用返回结果,就没有跨域问题了。

8)跨域-代理解决

  • 只要协议、主机、端口之一不同,就不同源,例如
    http://localhost:7070/ahttps://localhost:7070/b 就不同源
  • 同源检查是浏览器的行为,而且只针对fetch、xhr请求,
    a、如果是其它客户端,如java http client,postman,它们是不作同源检查的;
    b、通过表单提交、浏览器直接输入url地址这些方式直接发出的请求,也不会做同源检查;
  • 更多相关知识请参考js相关网站MDN中的一篇文章
    MDN | 跨域资源共享

后端资源,也通过前端代理查询;解决思路:
在这里插入图片描述

补充:用nginx做代理,无论访问前、后端资源,都先访问nginx,是不是也和上述解决思路相同?是不是也能解决跨域问题?

Express按照代理插件
在这里插入图片描述
在main.js中引入插件
在这里插入图片描述
创建代理
在这里插入图片描述
上面完成后,重启Express服务器。

8、js的api的导入导出

背景
当功能大到一定程度,单个文件完成所有功能就不太好了,往往需要在不同的文件实现不同的功能;
如何实现在一个js文件,引入另一个js文件中的变量、api?
这就需要用到api的导入、导出了。

1)单个导入、导出
在这里插入图片描述
在另一个html文件中,单个导入
在这里插入图片描述
2)一齐导入、导出
在这里插入图片描述
导入不变

3)导出默认
在这里插入图片描述

注意,export关键字,若不带default,在一个js文件中可以出现多次。
但是,如果export带default,那一个js文件中只能出现1次。
在这里插入图片描述
导入默认时,因为只有一个,所以不用再加大括号;
另外,导入默认时,名字可以随便起,不用与导出处相同。

4)整体导入
在这里插入图片描述
5)两种导入标签的对比

//	第一种导入标签
<script src='1.js'>
	...
</script>

//	第二种导入标签
<script type='module'>
	import {a, b, c} from './1.js'
	...
</script>

区别一:
第一种导入标签<script src='1.js'></script>,是一种比较老的导入语法,不支持前面说的那种export导出内容的导入;
第二种标签<script type='module'></script>,支持前面说的通过export导出内容的导入。

区别二:
通过第二种方式导入,要求导入内容、必须和当前文件同源,也就是要遵循“同源策略”。
但是第一种导入方式,是可以导入非同源js的。

补充:前面通过export导出,通过import导入的js语法,不仅会用到script标签中,还会用到js文件中,它是一种比较新的js语法,是现在主流的用法。

ts

1、基础知识

1)使用背景

js是动态语言,在有些场景下会出问题。
比如,结构入参不定义类型,会给使用者造成调用困难;使用者不一定知道入参的初始设计,如果入参格式不符合初始设计,可能会造成错误;
更可怕的是,这个错误在编译时不会被发现,到了运行时才会报错。
在这里插入图片描述
在这里插入图片描述

使用ts有两个好处:

  • 因为限定了参数格式,开发工具可以提示给我们所有的类型api,方便开发;
  • 调用者不会再使用错误类型的入参,导致运行时异常;
    在这里插入图片描述

2)按照ts环境

ts代码并不能直接被运行,它需要经过一个编译器编译,把它的代码编译成js,也就是项目最终运行的还是js,ts只是在编译的时候做类型检查;
安装ts编译器的命令

npm install -g typescript

2、ts类型标注位置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但并不是所有函数的参数都需要限定类型,比如

const names = ['Alice', 'Bob', 'Eve']
const lowercaseNames = names.map( (e) => e.toLowerCase() )

上面map中的匿名函数,因为其入参取的是数组的每个元素,其类型已经确定,所以并不需要限定类型。

当然,你限定了也能行

const names = ['Alice', 'Bob', 'Eve']
const lowercaseNames = names.map( (e) => e.toLowerCase() )

在这里插入图片描述
但是,标注在返回值的类型声明一般也可以省略

总结
类型声明可以标注在:变量、函数入参、函数返回值三个位置;但是一般只有函数入参的变量声明是需要的,其它两个位置的变量声明都可以省略。

3、ts类型

ts类型一:复杂类型

1)使用type关键字
在这里插入图片描述
在这里插入图片描述
定义了以后,字段少了、多了、类型与定义不同,都不对。

2)使用interface关键字
在这里插入图片描述
可以看到,与type关键字几乎一样的用法。

3)定义字段为可选属性

在这里插入图片描述
通过?:把字段定义为可选属性,也就是在定义变量为复杂类型Cat的时候,这个字段有也行、没有也行。

注意:当定义了可选属性后,就可能在调用的时候出现undefined的情况,要注意处理这种情况。

4)ts的鸭子类型
鸭子类型的核心思想是“如果它看起来像鸭子、走路像鸭子、叫声像鸭子,那么它就是鸭子”。

ts的类型检查,有时候并不是绝对严格的,它会遵循“鸭子类型”;即如果一个普通对象,它和ts的复杂类型很像的时候,就可以被视为ts的复杂类型。
在这里插入图片描述
如上图,变量c3并不是Cat类型,但它是一个普通对象、且与Cat类型很像,所以变量c3可以被当做Cat使用;
所以调用test(c3)不会报错,是正确的。

ts类型二:复杂类型

在这里插入图片描述
在这里插入图片描述

ts类型三:字面量与nulllish类型

1)
有这样一个需求:写一个printText函数,它会输出一段文字,并且可以根据参数决定打印内容的对齐方式;

如果我们写的函数如下:
在这里插入图片描述
调用时传参如下
printText(“Hellow”, “left”),这个调用是正确的,也能满足需求。

但是当我们入参不合法的时候,比如调用方式传一个“aaa”,肯定是没有这种对齐方式的,所以不正确,但是我们的函数还是可以被正常调用,并不能报错提示;说明上面函数的设计还是存在问题。

2)要满足需求,并且筛除对齐方式不合规的问题,可以对参数2进行双重限定,即限定它的类型是String、又限定它的取值范围,调用时无论违反哪个限定,都会报错。
在这里插入图片描述
如上图,传入非法参数就会报红;
而传入正确的入参,就没问题。
在这里插入图片描述

上面参数2,就是定义为了字面量类型
在这里插入图片描述

ts类型四:nulllish类型

1)
如下图中的函数,入参定义为String类型
在这里插入图片描述
但是这个string类型并不包括null类型,传null(或者不传,也就是传nullish)就会报错
在这里插入图片描述

2)要想支持接收null类型的入参,可以像字面量类似一样定义
在这里插入图片描述
但是上图可以看见,这样定义参数,调用的地方确实不报错了,但是函数内部报错了,因为x为null时调用toUpperCase()方法就会报错。
所以需要开发自己在函数内部进行处理
在这里插入图片描述
3)如果在上面的继承上,还想支持nullish,写成下面这样是不行的。
在这里插入图片描述
正确的入参编写方法是
在这里插入图片描述
从上图可以看到,改用?:定义入参类型后,用unllish调用时就不会报错了;
但是同样的,其函数内部还是需要手动处理一下unllish的情况
在这里插入图片描述
最后结合前面学习的只是,可以对其内部的js代码进行简化
在这里插入图片描述

从上面的例子可以看出,ts对null和nullish都有加强的判断,可以帮助我们写出更加安全的代码。

ts类型五:泛型

1)对象类型的泛型使用
对于如下代码,其写法有一些冗余
在这里插入图片描述
在这里插入图片描述
针对上面冗余的问题,可以通过泛型进行简化
在这里插入图片描述
在这里插入图片描述

2)函数类型的泛型使用

在这里插入图片描述
在这里插入图片描述
可以再针对泛型函数的返回值进行一下限定
在这里插入图片描述
使用示例如下
在这里插入图片描述
得到的v1变量就是泛型对象,上面例子是字符串,所以v1.value可以直接用字符串类型的api
在这里插入图片描述
同理,也可以写泛型是number的
在这里插入图片描述

4、ts的意义

  • 对于初学者来说,它可以让我们的代码有更好的“提示”

  • 可以更好的帮助我们理解前端框架
    因为现在很多框架内部,都是用了ts的,只有懂ts才能读懂。

比如说,看一下js框架的map类型
在这里插入图片描述
可以看出,map的set方法,返回值是this,所以set支持链式调用
在这里插入图片描述
在这里插入图片描述

ps1:前端读取api源码的方式

按ctrl键,鼠标再选中函数,点击,它就会弹一个小窗口列出源码文件列表;选中要看的,双击,就可以进入源码了

ps2:vscode分屏的方式

鼠标拖动文件,一直拖到最右边,松开就会分屏了

5、ts中的class语法(js中也有class语法)

1)其基础定义的示例如下,可以看出和java很类似,由字段、构造方法构成
在这里插入图片描述
使用的示例
在这里插入图片描述

但是这里要注意:js、ts中的class,它只是接近于java-class的写法,但其本质还是一个原型;
使用命令tsc hellow.ts编译之后就可以看出来,最终它还是生成了一个js类型的文件,文件中的代码也是:
在这里插入图片描述

2)class语法

2.1)属性是只读的,如果用obj.属性 = value为其赋值,会报错,提示改属性是只读的;
在这里插入图片描述

2.2)在class中定义方法的示例
在这里插入图片描述
class方法的调用,和java类似
在这里插入图片描述
2.3)get、set属性
这里get、set属性,很像一个方法,注意方法名要和字段区分开
如下图,字段名用_name,约定为只读的属性;
方法名用name
在这里插入图片描述
使用示例
在这里插入图片描述

2.4)类与对象的共用
在ts中,对象可用interface表示
在这里插入图片描述
与java类似,类可以实现interface
在这里插入图片描述
当class实现interface时,不仅要重写interface的字段,还要重写interface的方法,否则就会报ts错误。

使用示例
在这里插入图片描述
编译命令
在这里插入图片描述
2.5)继承与接口共同使用
与java类似,在ts中class可以继承一个父类,同时实现多个interface。
在这里插入图片描述
在这里插入图片描述
调用示例
在这里插入图片描述
上面的使用示例没有定义变量类型,也可以限定一下变量类似;
但是这里要注意:

  • 如果变量类型限定为接口,那么变量b就只能使用fly方法,无法使用父类的name属性;
  • 如果变量类型限定为子类,那么变量b就既可以使用fly方法,又可以使用name属性;
    在这里插入图片描述
  • 另外,也可以把变量类型限定为以下这种写法,这样也可以即调用方法、又调用属性
    在这里插入图片描述
    2.5)方法重写
    ts的class语法与java类似,子类继承父类,可以重新父类的方法
    在这里插入图片描述
    使用示例
    在这里插入图片描述
    如上面使用,会先打印“father study”,因为重新的study方法写了super.study()
    最后打印“son study”

React

1、环境建设

1)搭建一个react项目架构
在目标目录,进入cmd,输入命令

npx create-react-app p_name --template typescript

其中p_name是自定义的项目名,如果目录下没有,会新建;
后面的--template typescript可以没有,需要使用ts规范的加。

命令执行完成,会提示进入项目执行npm start命令进行初始化
在这里插入图片描述
执行以后,会启动新建的react项目,并自动打开浏览器进入ui首页,默认端口是3000
在这里插入图片描述

在这里插入图片描述
用vscode打开当前项目
ps:可以用命令让vscode打开当前项目:首先进入项目;
然后输入命令code .(实际测试,有时候行、有时候不行)

2)修改项目的监听端口

在项目的根目录下,新建一个名为.env.development的文件,它可以定义开发环境下的环境变量
在这里插入图片描述
在文件中写入端口配置

PORT=7070

修改后,打开终端、npm start重启项目,端口就变成了7070
在这里插入图片描述

3)装浏览器扩展插件

react 开发者工具

安装成功后,在浏览器使用了react的页面,点F12可以看到控制台会多出两个选项卡
在这里插入图片描述

2、函数式组件/组件标签/react组件/react标签

1)个人理解:

react的组件component,也叫“函数式组件”;其使用方式类似于函数,只是调用方式有区别。
例如组件名叫Hellow,它就类似就是函数名、等着被调用,只不过函数通过Hellow()这种形式调用,而react组件通过标签调用:

<hellowword />

2)组件标签与html标签的区分

在react开发中,常常会在同一个页面下、既有html标签,又有组件标签(自定义or外部引入的),为了方便区分,要注意它们的命名规则:

html标签一定要用小写的;
组件标签要用大驼峰命名(首字母大写)

2.1、组件参数

还是类比函数,函数可以传参数,react组件也可以传参数,只不过函数通过参数列表传参数,而react组件通过标签属性传参数。

1)通过以下示例,说明函数式组件的定义、使用方式

组件的定义
在这里插入图片描述
组件的使用
在这里插入图片描述
效果
在这里插入图片描述
注意,string类型参数可以用'' 或 ""赋值,而其它类型诸如number等均需要通过{}赋值

2)当需要的参数有多个时,一般不会定义多个参数;而是通过定义一个对象类型,通过在对象中定义多个属性、来接收多个参数。

3)参数解构

结合前面学js时学的,对象类型通过{}解构,可以省去大量的props.属性,改为直接通过属性名调用
在这里插入图片描述
在这里插入图片描述
调用处不变。

再复杂一点的,入参有多个,且其中有普通类型、有对象类型,也是可以的。

export default function Hello({msg, age, class}: {msg: string, age?: number, class: {classno: number, classname: string}})

组件标签-参数-使用总结

在这里插入图片描述
在这里插入图片描述

2.2、组件内实现标签绑定事件

写一个名为P1的组件,这个组件要展示图片、人物信息,并且为其中的图片绑定一个单击事件。

实现如下:自定义的组件标签中,用到了一个img标签,给img标签绑定一个点击事件;
该事件指向一个执行函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3、组件内实现标签循环

需求:
在这里插入图片描述
2)实现:
在这里插入图片描述
上图中的实现没有问题,但是在react组件中不能直接返回数组标签,直接返回数组标签会报错;
在这里插入图片描述
如果是数组标签,可以将它们放入一个父标签返回。
在这里插入图片描述
但是如上述方式返回,会导致凭空多了一组父标签。
在这里插入图片描述
这组多出来的父标签,可能会影响我的页面布局,有时候我们不想要这组父标签,这种情况下怎么处理呢?

可以用React.Fragment这种特殊的标签,它只起到占位的作用,并不会生成真的标签
在这里插入图片描述
在这里插入图片描述
如果觉得React.Fragment不好记,还可以用它的简化写法,就是一个空标签。
在这里插入图片描述

2)
按照上面的写法,还有一个警告错误,可以在控制台看到
在这里插入图片描述
它是说,jsx标签需要有一个唯一的key属性,现在我们生成jsx标签

没有加这个属性,所以报了这个警告错误

按要求补上key属性即可,主要要保证key唯一
在这里插入图片描述

3)实际业务的应用

需求:入参是一个student数组,要根据这一组信息、通过一组P1标签对其做展示。
逐个调用P1即费时费力,又需要大量代码,且逻辑复杂、容易出错。

实际业务中的解决思路:
定义一个P2组件,组件接收student数组入参,把数组入参处理后,形成P1需要的入参、循环调用P1组件,最终返回循环后"实例化"后的P1组件;
这里面,P1是一个jsx标签,P2是另一个jsx标签,可以看到一个jsx可以用到其它jsx,最终组合出功能强大的组件。
在这里插入图片描述
在这里插入图片描述

2.4、组件内使用条件判断

1)需求:
在前面根据student数组生成一组P1实例化组件的基础上,想要通过多加一个入参、限定年龄展示or不展示。

可以想到,新增一个入参,那么调用处、P2都需要新增入参,但是不做处理、只是传递;
P1接到入参,判断满足展示的条件、就展示年龄,满足隐藏的条件、就隐藏年龄。

使用的地方传参:
在这里插入图片描述
P2接收参数,并传给P1:
在这里插入图片描述
在这里插入图片描述

P1接收参数,两个入参(组成一个对象),第一个入参本身也是一个对象(自定义对象);第二个入参为可选属性
在这里插入图片描述
条件判断-方式一:使用if判断
在这里插入图片描述
条件判断-方式二:使用逻辑与,老师说这种在实际开发中更常用
在这里插入图片描述

ps:ageFragment是字符串类型,在react中用{}使用它,这里的{}类似于mybatis中的#{},是一个占位符,值会随着变量变化而变化;
如果这个字符串的值是html标签,在浏览器展示的时候,它就会被当做标签渲染展示

逻辑与说明

在这里插入图片描述
从前往后判断,如果&&前的值判断是false就结束判断、返回第一个值(因为第一个已经不满足逻辑与,所以结束判断,返回当前正在判断的值、这是代码设定的逻辑),如果&&前的值判断是true、就接着判断&&后的值;
总结:从前往后判断,直到遇到false或者到达最后停止,遇到flase就返回当前值,走到最后就返回最后那个值。

2.5、axios/acsios/——动态处理数据

ps:npm install就是下载依赖,类似于java中用maven下载依赖;npm install xx就是下载xx依赖。

1)
axios是一个第三方库,它提供了发请求、接响应这些功能,帮助我们从服务器获取数据;安装命令:

npm install axios

安装好之后,就会在package.json文件中(它就像java中的pom文件),自动新增一个axios依赖、并注明其版本
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
并且,在node_modules(类似于java中的lib、或者本地依赖库)将axios安装好。

2)
axios有很多api,用法可以参考它的文档 Axios API
这里写一个用axios发get请求获取数据的最佳实践:

import axios from 'axios'
import { Student } from '../model/Student'

interface R<T> {
	code: number,
	date: T,
	message?: string
}

export default function P3({id}:{id:number}) {
	async function getStudent(){
		const resp = axios.get<R<Student>>('http://localhost:8080/api/student/query/${id}')
		console.log(resp.data.data)
	}
}

调用
在这里插入图片描述

当然,上面代码还可以优化一点,就是把R这个对象,也和Student一样,放在model包下,这里引用进来。
上面是为了清晰,才写在一块儿的。

2.6、函数无状态

1)
假设现在有这样的需求;新建一个react组件P4,它从后端获取数据、并展示(返回一个用于展示的html标签);
我们在一个tsx页面调用这个P4组件;
这样当我们访问这个tsx页面的时候,就能显示从后端过来的数据。
P4组件这样实现:

import axios from 'axios'
import { Student,R } from '../model/Student'

export default function P4({ id }:{ id:number }) {
	async function getStudent(){
		const resp = axios.get<R<Student>>('http://localhost:8080/api/student/query/${id}')
		student.name = resp.data.data.name
	}

	const student = {name:'xx'}

	getStudent() //	执行调用

	return <h3>{student.name}</h3>
}

在index.tsx里边这样调用:
在这里插入图片描述
结果却发现,页面开始显示初始值“XX”,后面后端数据来了,还是显示“XX”、并没有更新成获取的数据;
这是为何?

2)
在上述场景中,react组件的执行顺序:
首次使用到P4组件,就会执行P4函数,函数返回的html作为tsx页面显示内容;但注意:

  • 如果函数没有再次调用,那么就不会更新页面;
  • 如果函数调用了,但返回结果不变,页面也不会更新;

上面例子中,之所以一直显示初始值“XX”,首先就是因为没有在响应返回后、重新执行P4函数;

在react中,想要触发页面更新/重新渲染(触发重新调用组件),需要满足以下条件之一:

  • 需要props发生改变
  • 需要state发生改变

3)首先试一下通过改变props来触发重新调用组件:
想要改变props,可以通过浏览器-react扩展插件实现,如下图手动修改组件的props、比如把id从1改成2
在这里插入图片描述
改完之后重新看控制台,确实再次执行了组件函数
在这里插入图片描述
但页面显示却没有发生变化,仍然是初始值“XX”

这是因为上面的组件代码,局部变量student每次执行、都会被重新赋初始值,所以后面虽然又执行了一遍组件函数,但是student又被重新赋值“xx”,响应值还是没有被展示到页面上。

上面问题的本质,其实是组件函数P4是一个无状态的函数,无状态的意思就是:每次执行,函数都会被恢复到初始状态;
实际生产开发中,和上面的例子类似,我们常常希望能把函数变成有状态的;也就是响应可以阻塞、后续慢慢执行,但是最终执行得到的结果要被保存下来,组件函数再次被执行的时候,特定变量要从新的值开始执行、而不是每次都被初始化

结论:通过改变props来触发重新调用组件,可以实现重新调用,但是由于函数是无状态的,所以不能把从后端获取的数据渲染到页面上;
为了实现更新页面展示值的目标,需要再试一下能不能通过改变state来实现。

2.7、useState

1)怎么理解组件函数的state?

state直译是“状态”;
其实应该把它理解为函数组件之外的一个变量,类似于java项目的全局缓存、redis缓存、分布式缓存之类的。
要让函数组件有状态,其实就是有些变量要不断向前更新,不能每次调用又回退到初始值。
比如,我们要让P5组件中、student的值有状态,那么就把student放到state里边管理;
student的初始值是“xx”,过了一会后端响应过来了,student的值就变成了返回的“石勒”;
当再次调用P5的时候,student的值就不是初始的“xx”了,而是保持状态的“石勒”;
再接着二次调用的响应返回了,student的值又变成了“石虎”,也会被保持…

2)useState的使用

新建一个组件P5,功能和前面的P4一样,但是使用useState

import axios from 'axios'
import { useState } from 'react';
import { Student,R } from '../model/Student'

export default function P3({id}:{id:number}) {
	async function getStudent(){
		const resp = axios.get<R<Student>>('http://localhost:8080/api/student/query/${id}')
		setStudent(resp.data.data)	//	响应回来,更新状态
	}
	
	/**
		参数:数据的初始值
		返回值:[a, b]
			a: 状态数据
			b:用来修改状态数据的方法
	*/
	const [student, setStudent] = useState({name:'xx'})

	getStudent()

	return <h3>{student.name}</h3>
}

上面的注释说,useState()返回值是一个数据,第一个元素是状态数据,第二个元素是用来修改状态数据的方法;
这里有个问题?
既然第一个元素就是状态数据,我们为什么不通过给它赋值来直接修改状态数据,而是还要用一个专门修改状态数据的方法呢?

因为直接赋值,不能触发组件被重新调用,而使用修改方法可以触发组件被重新调用;
前面说,触发组件函数被重新调用有两个方式:1)props改变,2)state改变;
这里说的state改变,就是指通过调用useState返回的修改方法改变state,才会触发组件函数被重新调用,也才能使页面被重新渲染。

上面是代码表名上看没什么问题了,实际有一个很大的漏洞:
在定义了useState后,会执行函数getStudent()的调用,而调用得到响应后、会使用setStudent方法,进而又会触发整个P5组件被重新执行;
这样,就会造成请求被周而复始的执行,非常不好…
在这里插入图片描述

这个循环调用的问题怎么解决呢?
老师说后面会学习useEffect,但是这里可以先用一个土方法解决:再定义一个布尔类型的变量,并把它放到state里边;
根据它的状态,觉得要不要调用setStudent方法。

	const [student, setStudent] = useState({name:'xx'})
	const [fetch, setFetch] = useState(false)

	if(!fetch) {
		setFetch = true
		getStudent()
	}

	return <h3>{student.name}</h3>

X、下一课

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值