直到上节课我们终于是把我们之前的小需求给完成了,总共在一起历时5个课时,可能有人要说,就一个state
的应用讲了这么多的课时也太多了一点。当然了,我们为的是能够理解为什么要这么用,只有理解了,后面的知识学起来才不会那么吃力。
通过前面课时的学习大家可能已经发现了,我们的react
好像对原生js
的要求还挺高的。所以说大家有空可以把原生js
的相关内容再复习一下。
回顾
今天这节课呢,我们接着往后学习有关我们类组件的简写方式,在开始今天这节课的学习之前,我们读上节课的内容做一个简单的回顾:
react
不支持直接修改state
,必须通过setState
方法来修改state
setState
存放在React.Component
的原型对象上setState
方法接收的参数是一个对象setState
方法并不是直接全局替换掉原来的state
而是合并- 整个过程中类组件的构造器方法只被调用一次
render
方法被调用1 + n
次
为什么要简写
在正式学习之前或许有人会不明白,我们都已经可以正常使用state
了,为啥还要去看什么简写呢?
我们来看一下我们之前的代码:
class Weather extends React.Component { constructor(props) { super(props); this.state = { isHot: false }; this.changeWeather = this.changeWeather.bind(this); } render() { return ( <h2 onClick={this.changeWeather}> 今天天气很{this.state.isHot ? "炎热" : "凉爽"} </h2> ) } changeWeather(){ const isHot = this.state.isHot; this.setState({ isHot: !isHot }); } }
大家看一下,这些代码写得是不是完全符合要求,也没有违背什么规则,功能也实现了。但是有没有感觉代码稍微繁琐了一些?
当然这种代码的可读性是真的很好,可维护性确实很高,但是在工作中日常开发这样来写代码会拖慢我们的工作进度的。
那我们就要对我们的代码来进行相关的精简。
需要简写的部分
我们回头看一下我们的代码结构。这个类组件里面有构造器,render
还有我们自定义的changeWeather
方法是作为回调的。
再回想一下构造器里的this
是什么?是不是Weather
的实例对象?那render
方法里的this
呢?是不是也是Weather
的实例对象?构造器的this
是Weather
的实例对象我们可以理解。但是为什么render
方法中的this
也是?因为react
在渲染页面的时候实例化了Weather
并且通过Weather
的实例对象调用了render
方法。所以说render
中的this
也是Weather
的实例对象。
但是changeWeather
这个函数是我们自己定义的,在前面我们也花了挺大的篇幅来讲解这个方法中的this
指向问题。这里也就不再赘述了。
但是我们回想一下上面的几个结构,react
是不是就只调用了构造器方法和render
方法?我们自定义的方法是用来作为事件监听器的回调,但是只要作为监听回调的方法,this
指向都会丢失,我们都需要在构造器中写上this.method = this.method.bind(this)
。那么,如果我这个组件里有十几个回调方法的话,那么我们就要在构造器中写十几行这种方法赋值语句。所以说这是我们代码精简的一个方面。
那我们在看看state
的初始化,我们的大标题就是说state
的简写,那么我们怎么能把我们的主角儿给漏了呢?
我们会过头来想一想,我们在初始化state
的时候是不是借助了构造器,构造器里面在我们没有给回调方法赋值的时候是不是就只做了state
的初始化?我们也说过构造器并不是必须的,只不过我们不知道初始化state
除了在构造器里还能在哪写,所以不得已才写了构造器方法。那么这也是我们要精简的一个点。
精简state
前文已经归纳出了需要精简的部分,那么我们就正式开始吧:
我们首先想一下,我们在实例化类的时候要把state
初始化。不在构造器里写我们怎么办呢?我们来回忆一下类的相关的知识。
class Adult {}
比如我这么定义了一个Adult
类,我不写构造器,那么我实例化Adult
的话得到的实例化对象里面是不是什么都没有?如果我这个时候想让Adult
有一个写死的属性年龄默认18
岁,那么我们是不是要写个构造器在里面设置一个属性为18
?这是我们的常规写法,但是我们接下来看一个新写法,我不写构造器的话,直接在类里来直接赋值
class Adult { age = 18; }
看上面这段代码,或许有人要问了,怎么?age
前面不需要加let
关键字吗?我们要注意的一点,类的作用域内,和函数体作用域是两码事。这里如果加了let
关键字就会报错的。
报错信息告诉我们,在这里发现一个不支持的标记,类中只支持构造器,方法,访问器或者属性。
但是上面这端代码到底是什么结果呢?
从图中我们可以看到我们实例化出来的对象中有了age
属性。所以说在类中直接写age = 18;
这一步就是在给实例本身添加了一个写死了的属性。属性名为age
值为18
.
所以我们可以得出一个结论就是,类的作用域里面可以直接写赋值语句,但是不能够使用声明变量时所用的关键字。而且这个赋值语句是在给该类的实例对象本身添加属性。
但是我们要注意的一点,如果在实例化对象时要将某值传入这个类并添加为属性的话,那么就必须借助构造器了。
那么好,我们在初始化state
的时候有没有从外面传state
的值进来?没有的啊,我们的state
是在构造器里面写死的啊,那我是不是可以把state
从构造器方法中拿出来?直接在外面赋值就行了。那我们来看一下代码:
class Weather extends React.Component { constructor(props) { super(props); this.changeWeather = this.changeWeather.bind(this); } state = { isHot: false }; render() { return ( <h2 onClick={this.changeWeather}> 今天天气很{this.state.isHot ? "炎热" : "凉爽"} </h2> ) } changeWeather() { const isHot = this.state.isHot; this.setState({ isHot: !isHot }); } }
这样我们是不是就把state
从构造器里面拿出来了?
精简类方法绑定this
可能有人要问了,行,state
这一行从构造器里面拿出来了。那么这个changeWeather
赋值那一行怎么办?还是在构造器里面啊。那我是不是可以不写啊?当然不行啊,不写的话changeWeather
方法里面的this
指向是不是又丢了啊,那我们怎么办?
我们再来分析一下,之前内容是不是说我们定义的方法都在类的原型对象上供实例对象调用啊?而我们在构造器方法中this.method = this.method.bind(this)
是不是通过原型链把原型对象上的方法赋给了实例对象上的属性了?
而属性的赋值只要不从外面传入是不是都快要在类里面直接赋值而不需要写到构造器中?那么好办了,我们这么写:
class Weather extends React.Component { state = { isHot: false }; render() { return ... } changeWeather = function() { const isHot = this.state.isHot; this.setState({ isHot: !isHot }); } }
我们看上面这段代码是什么意思,我是不是在Weather
类组件的实例对象上添加了一个changeWeather
属性,而这个属性是一个函数?
那么这个changeWeather
放在哪了?是不是放在Weather
的实例对象上?而Weather
的原型对象上还有没有changeWeather
方法?那肯定没有了啊。那我们来验证一下:
图中输出了Weather
的原型对象,我们可以很直观地看见Wether
的原型对象上面已经没有了changeWeather
方法。因为我们并没有定义这个方法,我们只是把一个匿名函数赋值给了实例对象自身的一个叫做changeWeather
的属性。
或许有细心的朋友已经开始怀疑了,这样行吗?
那我们来试一下:
我们一点击,完蛋,又报错了,而且从报错信息上看这又是我们最头疼的this
指向丢失了。这不扯犊子呢嘛,我这不就只是把函数换了个地方,但是this
的问题没有给我解决。那么我吗换一个写法:
class Weather extends React.Component { state = { isHot: false }; render() { return ... } changeWeather = () => { const isHot = this.state.isHot; this.setState({ isHot: !isHot }); } }
大家来看,我只是把普通匿名函数改成了箭头函数,这样能行吗?我们试一下就知道了:
这下大家可以看到我们点击就没有问题了,可以正常切换,也没有报错。但是为什么我们换了一个写法就没有问题了呢?
因为箭头函数有一个特点就是箭头函数没有自己的this
,如果在箭头函数中使用this
这个关键字的话,箭头函数会去找自己外层函数的this
并且当成自己的来调用。那么这里这个箭头函数的this
指向是什么?我们不知道,我们是不是要去找这个箭头函数的外层函数是什么?我们也不知道,因为这个箭头函数是写在类里面的,不是在函数里面写的,那么我们从这个箭头函数往外层找,是不是就找到了整个类作用域?那么我这个类的作用域中this
指向的到底是什么呢?我们打印一下:
但是,这里报错了,因为类里面不支持这么操作,类中只支持构造器,方法,访问器或者属性。console
是什么?是一个对象,它不属于上面所说的任何一个东西,那我们怎么办,我们就不知道类的这个游离的作用域空间中的this
是什么了。
没有关系,我们不是说了嘛,箭头函数没有自己的this
,在箭头函数中使用this
的话,就会去找箭头函数的外层,使用这个外层里面的this
那么我们这里的箭头函数的外层是不是就是这个类的游离的作用域空间?那么我们这个箭头函数中的this
那是不是就等价于这个类的作用域空间中的this
?那我们直接打印箭头函数中的this
就可以了。来看一下这个this
到底是什么:
诶,我们从图上可以看出来,这里的this
就是Weather
的实例对象。那这样的话我们用箭头函数是不是就可以解决this
指向丢失问题了?
或许又有人很好奇,那我就必须要用箭头函数吗?我就真的没有别的方法了吗?有好奇心是好事。那大家来看我下面这段代码:
class Weather extends React.Component { state = { isHot: false }; render() { return ... } changeWeather = (function() { const isHot = this.state.isHot; this.setState({ isHot: !isHot }); }).bind(this); }
我这样写行不行?这样是不是和之前构造器函数中this.method = this.method.bind(this);
类似?没错,这两种写法是同一个原理,你看我们这里的bind
接收到的this
是什么?是不是Weather
类作用域空间里的this
?我们刚才已经介绍过了,类作用域空间里的this
就是这个类的实例对象,那么就完成了整个流程:
- 定义一个匿名函数
- 通过
bind
生成一个新函数 - 将生成的新函数中的
this
指向改为Weather
的实例对象 - 将新函数赋值给
Weather
实例对象上的changeWeather
属性
但是为什么我们不这么写呢?这么写撕吧撕吧太繁琐了啊?我们还要记得用()
括起来,后面再加一个bind
,如果我们忘了呢?还要等发现报错再来修改。哪有我们直接一个箭头函数解决了方便啊。
好了,以上便是我们的代码精简的相关内容。
总结
- 标准写法比较繁琐,在实际开发中会拖慢我们的进度,所以要进行精简
- 精简后的代码结构分为三个部分,就目前来说,不需要构造器,至于后面要不要我们再说
- 赋值语句直接初始化状态
render
方法- 自定义方法,所有自定义方法全部使用赋值语句加箭头函数来写
- 箭头函数自身没有
this
- 箭头函数中如果要使用
this
,那么就会去查找箭头函数外层的this
并作为自己的this
来使用
Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.
Link of this article:https://work.lynchow.com/article/simple_state/