React系统学习(組件三大核心属性之state)

之前简单学习的react中函数式组件和类式组件的基本使用,函数式组件适用于定义简单的组件,类式组件适用于定义复杂的组件。

复杂的组件指的是有状态的组件,所谓的状态,在react中就是三大核心属性之一的state。

从一个小例子说起

假设需要这个一样组件,访问的时候展示一个人的名字和心情,当点击的时候就切换到另一个人,再次点击就再切换回去。

那么对这个需求进行分析,就会知道首先肯定是需要有地方进行信息展示,然后就是肯定需要有点击事件,再然后就是需要有地方保存数据。

基于上边的分析,在还不是太会react的情况下,基于之前的js基础,这个组件一开始可能会先做如下这样的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<body>
<div id="test"></div>
<!--核心库-->
<script type="text/javascript" src="../../js/react.development.js"></script>
<!--操作dom-->
<script type="text/javascript" src="../../js/react-dom.development.js"></script>
<!--用户将jsx转为js-->
<script type="text/javascript" src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

<script type="text/babel">
// 1.类式组件
class ClassComponent extends React.Component{
name='tuzongxun'
mood='happy'
render() {
return <h1 onclick="changeUser()">I'm {this.name},today i'm {this.mood}</h1>
}
changeUser(){
if(this.name === 'tuzongxun'){
this.name='tzx'
this.mood='unhappy'
}else{
this.name='tuzongxun'
this.mood='happy'
}

}
}
// 2.渲染虚拟DOM
ReactDOM.render(<ClassComponent/>,document.getElementById('test'))

</script>
</body>

然后当在浏览器访问的时候,就会发现点击根本是没有反应的,如果打开浏览器控制台,就会看到有如下的错误提示:

1
2
3
Warning: Invalid event handler property `onclick`. Did you mean `onClick`?
in h1 (created by ClassComponent)
in ClassComponent

很显然,意思是上边的onclick用法不对,在react中应该是onClick,其实就是on后边的c大写。

类似onclick这种事件,在react中都需要这样写。

但是,上边代码的问题却不仅仅是这样,当把onclick改为onClick后,会看到控制台会有新的错误信息:

1
2
3
Warning: Expected `onClick` listener to be a function, instead got a value of `string` type.
in h1 (created by ClassComponent)
in ClassComponent

这句也比较好理解,就是说onClick后边应该是一个函数,但是这里却是一个字符串。

根据之前react的基础知识可以知道,这里应该是要把引号改成大括号,之后代码就成了这样:

1
2
3
render() {
return <h1 onClick={changeUser()}>I'm {this.name},today i'm {this.mood}</h1>
}

然而,这样改了之后会发现继续报错:

1
2
Uncaught ReferenceError: changeUser is not defined
at ClassComponent.render (<anonymous>:34:28)

实际上,这里正确的写法应该是onClick={this.changeUser},为什么不能加括号呢,是因为加了括号相当于调用了函数,也就相当于把changeUser函数的返回值赋值给了onClick事件,而不是函数本身。

上边给出了onClick定义的正确写法,但是实际上这个程序依旧不行,初始访问正常,当点击的时候就会发现又有了新的异常:

1
2
Uncaught TypeError: Cannot read property 'name' of undefined
at changeUser (<anonymous>:44:22)

这又是为什么呢?我的代码里明明是用了this的,难道这个this不是我这个组件的实例对象吗?

实际上,确实不是。

如果在changeUser函数中打印一下this,就会看到打印的就是undefined

可是,这又是为什么呢?

其实原因也很简单,是因为在事件这种定义中,虽然看起来是this.changeUser,但是实际是先把changeUser函数给了onClick事件,那么当点击的时候,这就变成了函数直接调用,而不是实例进行的调用。

直接调用,就相当于是在外边的js中直接写了changeUser(),而这时候,里边的this实际是window对象,但是由于react中会使用严格模式,就有不允许直接使用window对象,因此这个this也就变成了undefined

要解决这个问题,就需要在构造函数中使用bind,即:

1
2
3
4
constructor(){
super()
this.changeUser=this.changeUser.bind(this)
}

这样改完之后,整个js中的代码就成了这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script type="text/babel">
class ClassComponent extends React.Component{
name='tuzongxun'
mood='happy'

constructor(){
super()
this.changeUser=this.changeUser.bind(this)
}
render() {
return <h1 onClick={this.changeUser}>I'm {this.name},today i'm {this.mood}</h1>
}
changeUser(){
console.log(this)
if(this.name === 'tuzongxun'){
this.name='tzx'
this.mood='unhappy'
}else{
this.name='tuzongxun'
this.mood='happy'
}

}
}
ReactDOM.render(<ClassComponent/>,document.getElementById('test'))

</script>

浏览器访问不在报错,鼠标点击也不在报错。

然而,虽然是不报错,却会发现点击的时候根本就没有反应。

但是打开控制台的输出,却又可以看到输出的this确实已经被修改成功。

也就是说,我修改的数据,但是react并没有为我重新进行渲染。

怎么办呢?这就要轮到主角state上场了。

state基础用法

要让react在数据改变好自动渲染页面,需要把相应数据放在state中,也就是状态驱动渲染,主要涉及到两个方面,一个是数据的存储,另一个是数据的修改。

进行修改后的组件定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ClassComponent extends React.Component{

constructor(){
super();
this.state={name:'tuzongxun',mood:'happy'}
this.changeUser=this.changeUser.bind(this);
}

render() {
return <h1 onClick={this.changeUser}>I'm {this.state.name},today i'm {this.state.mood}</h1>
}

changeUser(){
if(this.state.name === 'tuzongxun'){
this.setState({name:'tzx',mood:'unhappy'})
}else{
this.setState({name:'tuzongxun',mood:'happy'})
}
}
}

上边代码中修改的地方主要有两个,一个是把初始数据的定义放在了构造方法中,同时用上了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的赋值,并不是必须要写在这里边,拿到外边也是可以的。

所以看来看去似乎就只有一个是必须写的,也就是bindbind的作用可以理解为就是生成一个新的函数,同时修改函数里的this指向。

为什么要这个bind呢,原因是如果不写,那么一开始changeUser函数里的this指向的实际是window对象,在严格模式下就成了undefine,所以这里需要使用bindthis指向我们希望指向的对象。

看起来似乎这一行必不可少,这个构造函数也必不可少,但是实际不是的。

并且,上边这样写是针对一个函数,那么如果有十几个甚至几十个函数呢,也就会造成构造函数中这种代码非常多,这也是不太好的。

在js中有一种写法叫箭头函数,其实也就是相当于java中的lambda表达式。

箭头函数本身是没有this的,里边如果有this,则会指向函数定义的外侧this

因此,进一步简写后,上边组件的定义则可以改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ClassComponent extends React.Component{

state={name:'tuzongxun', mood:'happy'}

render() {
return <h1 onClick={this.changeUser}>I'm {this.state.name},today i'm {this.state.mood}</h1>
}

changeUser = ()=>{
console.log(this)
if(this.state.name === 'tuzongxun'){
this.setState({name:'tzx',mood:'unhappy'})
}else{
this.setState({name:'tuzongxun',mood:'happy'})
}
}
}

js中绑定事件的三种方式

在上边的例子中,使用到了点击事件,实际上js中点击事件的写法至少有三种,上边的写法只是其中一个,由于简洁,可能也是最常用的一个。

既然知道至少有三种方式,那么这里就顺便对这三种做一个简单记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<button id="btn1">button1</button>
<button id="btn2">button2</button>
<button onclick="click3()">button3</button>

<script type="text/javascript">
var button1=document.getElementById('btn1')
button1.addEventListener('click',()=>{
alert("click button1")
});

var button2=document.getElementById('btn2')
button2.onclick=()=>{
alert("click button2")
}

function click3(){
alert("click button3")
}
</script>
</body>

推荐文章