之前简单学习的react中函数式组件和类式组件的基本使用,函数式组件适用于定义简单的组件,类式组件适用于定义复杂的组件。
复杂的组件指的是有状态的组件,所谓的状态,在react中就是三大核心属性之一的state。
从一个小例子说起
假设需要这个一样组件,访问的时候展示一个人的名字和心情,当点击的时候就切换到另一个人,再次点击就再切换回去。
那么对这个需求进行分析,就会知道首先肯定是需要有地方进行信息展示,然后就是肯定需要有点击事件,再然后就是需要有地方保存数据。
基于上边的分析,在还不是太会react的情况下,基于之前的js基础,这个组件一开始可能会先做如下这样的定义:
1 | <body> |
然后当在浏览器访问的时候,就会发现点击根本是没有反应的,如果打开浏览器控制台,就会看到有如下的错误提示:
1 | Warning: Invalid event handler property `onclick`. Did you mean `onClick`? |
很显然,意思是上边的onclick
用法不对,在react中应该是onClick
,其实就是on
后边的c
大写。
类似onclick
这种事件,在react中都需要这样写。
但是,上边代码的问题却不仅仅是这样,当把onclick
改为onClick
后,会看到控制台会有新的错误信息:
1 | Warning: Expected `onClick` listener to be a function, instead got a value of `string` type. |
这句也比较好理解,就是说onClick
后边应该是一个函数,但是这里却是一个字符串。
根据之前react的基础知识可以知道,这里应该是要把引号改成大括号,之后代码就成了这样:
1 | render() { |
然而,这样改了之后会发现继续报错:
1 | Uncaught ReferenceError: changeUser is not defined |
实际上,这里正确的写法应该是onClick={this.changeUser}
,为什么不能加括号呢,是因为加了括号相当于调用了函数,也就相当于把changeUser
函数的返回值赋值给了onClick
事件,而不是函数本身。
上边给出了onClick
定义的正确写法,但是实际上这个程序依旧不行,初始访问正常,当点击的时候就会发现又有了新的异常:
1 | Uncaught TypeError: Cannot read property 'name' of undefined |
这又是为什么呢?我的代码里明明是用了this的,难道这个this不是我这个组件的实例对象吗?
实际上,确实不是。
如果在changeUser
函数中打印一下this
,就会看到打印的就是undefined
。
可是,这又是为什么呢?
其实原因也很简单,是因为在事件这种定义中,虽然看起来是this.changeUser
,但是实际是先把changeUser
函数给了onClick
事件,那么当点击的时候,这就变成了函数直接调用,而不是实例进行的调用。
直接调用,就相当于是在外边的js
中直接写了changeUser()
,而这时候,里边的this
实际是window
对象,但是由于react中会使用严格模式,就有不允许直接使用window对象,因此这个this
也就变成了undefined
。
要解决这个问题,就需要在构造函数中使用bind
,即:
1 | constructor(){ |
这样改完之后,整个js中的代码就成了这样:
1 | <script type="text/babel"> |
浏览器访问不在报错,鼠标点击也不在报错。
然而,虽然是不报错,却会发现点击的时候根本就没有反应。
但是打开控制台的输出,却又可以看到输出的this
确实已经被修改成功。
也就是说,我修改的数据,但是react并没有为我重新进行渲染。
怎么办呢?这就要轮到主角state
上场了。
state基础用法
要让react在数据改变好自动渲染页面,需要把相应数据放在state
中,也就是状态驱动渲染,主要涉及到两个方面,一个是数据的存储,另一个是数据的修改。
进行修改后的组件定义如下:
1 | class ClassComponent extends React.Component{ |
上边代码中修改的地方主要有两个,一个是把初始数据的定义放在了构造方法中,同时用上了state
,另一个是在changeUser
函数中改变了数据修改的方式。
数据存储
上边初始数据的定义,可以理解为就是数据的存储,之前是直接写在类里边,现在是换成了this.state={name:'tuzongxun',mood:'happy'}
.
需要注意的是,state
里边必须是一个对象。
数据修改
之前的问题就是虽然看起来数据每次都修改成功了,但是页面并没有变化,原因就是修改数据的方式不符合react的要求。
如果要让react在数据修改后就渲染页面,就必须用到setState
函数进行数据的修改,这个函数定义在React.Component
中。
实际上这个也很好理解,几乎所有自动化的东西,背后必然都是规则,只有遵循相应的规则了,才能被识别并自动化处理。
那么react
自动渲染的数据修改的方式,也就是类似上边代码里的this.setState({name:'tzx',mood:'unhappy'})
state简化写法
上边的代码从功能上讲是没有问题的,但是看起来有些繁琐,尤其是里边的构造方法,其实是不得已才写的。
构造方法中的super()
就不说了,这是js中类定义的基本规则。
而对state的赋值,并不是必须要写在这里边,拿到外边也是可以的。
所以看来看去似乎就只有一个是必须写的,也就是bind
,bind
的作用可以理解为就是生成一个新的函数,同时修改函数里的this
指向。
为什么要这个bind
呢,原因是如果不写,那么一开始changeUser
函数里的this
指向的实际是window
对象,在严格模式下就成了undefine
,所以这里需要使用bind
把this
指向我们希望指向的对象。
看起来似乎这一行必不可少,这个构造函数也必不可少,但是实际不是的。
并且,上边这样写是针对一个函数,那么如果有十几个甚至几十个函数呢,也就会造成构造函数中这种代码非常多,这也是不太好的。
在js中有一种写法叫箭头函数,其实也就是相当于java
中的lambda
表达式。
箭头函数本身是没有this
的,里边如果有this
,则会指向函数定义的外侧this
。
因此,进一步简写后,上边组件的定义则可以改成这样:
1 | class ClassComponent extends React.Component{ |
js中绑定事件的三种方式
在上边的例子中,使用到了点击事件,实际上js中点击事件的写法至少有三种,上边的写法只是其中一个,由于简洁,可能也是最常用的一个。
既然知道至少有三种方式,那么这里就顺便对这三种做一个简单记录:
1 | <body> |