终极目标:掌握和使用react

本博客目的:记录react学习的进度和心得(react状态)

内容:通过视频学习,掌握react基础语法。

前端框架react自学之react状态(六)

react state

相当于VUE的DATA,但是使用方式跟VUE不一致。

修改state时,使用setState方法去修改。直接修改state不会渲染

通过this.setState修改完数据后,并不会立即修改DOM里面的内容,react会在这个函数内容所有设置状态改变后,统一对比虚拟DOM对象,然后在统一修改,提升性能。

react组件的render方法

一个组件类必须要实现一个 render 方法,这个 render 方法必须要返回一个 JSX 元素。必须要用一个外层的 JSX 元素把所有内容包裹起来,返回并列多个 JSX 元素是不合法的。

案例

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React from 'react';
import ReactDOM from 'react-dom';


class Clock extends React.Component{
constructor(props){
super(props)//super关键字用于访问和调用一个对象的父对象上的函数。
//状态(数据)--》view
//构造函数初始化数据,将需要改变的数据初始化到state中
this.state = {
time:new Date().toLocaleTimeString()
}
//console.log(this.state.time)
}

render(){
//console.log("这是渲染函数")
//this.state.time = new Date().toLocaleTimeString();
return (
<div>
<h1>当前时间:{this.state.time}</h1>
</div>
)
}

//生命周期函数,组件渲染完成(挂载)时的函数
componentDidMount(){
setInterval(()=>{
console.log(this.state.time)
//this.state.time = new Date().toLocaleTimeString(); //错误的方式
//切勿直接修改state数据,直接state重新渲染内容,需要使用setState
//通过this.setState修改完数据后,并不会立即修改DOM里面的内容,react会在
//这个函数内容所有设置状态改变后,统一对比虚拟DOM对象,然后在统一修改,提升性能。
//小程序也是也是借鉴REACT状态管理操作
this.setState({
time:new Date().toLocaleTimeString()
})

},1000)
}

}


ReactDOM.render(
<Clock />,
document.querySelector('#root')
)

在ract组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件,而且react还会对这种引用进行缓存,以达到cpu和内存的最大化。在使用了es6 class或者纯函数时,这种自动绑定就不复存在了,我们需要手动实现this的绑定。

React事件绑定类似于DOM事件绑定,区别如下:

1.React事件的用驼峰法命名,DOM事件事件命名是小写

2.通过jsx,传递一个函数作为event handler,而不是一个字符串。

3.React事件不能通过返回false来阻止默认事件,需要显式调用preventDefault()

React组件方法中为什么要绑定this

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//假想定义一个ToggleButton开关组件
class ToggleButton extends React.Component{
constructor(props){
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick(){
this.setState(prevState => ({
isToggleOn: !preveState.isToggleOn
}));
}
handleChange(){
console.log(this.state.isToggleOn);
}
render(){
return(
<button onClick={this.handleClick} onChange={this.handleChange}>
{this.state.isToggleOn ? 'ON':'OFF'}
</button>
)
}
}
代码执行细节

​ 上例仅仅是一个类组件的定义,当在其他组件中调用或是使用ReactDOM.render( )方法将其渲染到界面上时会生成一个组件的实例,因为组件是可以复用的,面向对象的编程方式非常适合它的定位。根据this指向的基本规则就可以知道,这里的this最终会指向组件的实例。

​ 组件实例生成的时候,构造器constructor会被执行,此处着重分析一下下面这行代码:

1
this.handleClick = this.handleClick.bind(this);

此时的this指向新生成的实例,那么赋值语句右侧的表达式先查找this.handleClick( )这个方法,由对象的属性查找机制(沿原型链由近及远查找)可知此处会查找到原型方法this.handleClick( ),接着执行bind(this),此处的this还是指向新生成的实例,所以赋值语句右侧的表达式计算完成后,会生成一个指定了this的新方法,接着执行赋值操作,将新生成的函数赋值给实例的handleClick属性,由对象的赋值机制可知,此处的handleClick(右边赋值)会直接作为实例属性生成。

总结一下,上面的语句做了一件这样的事情:

把原型方法handleClick( )改变为实例方法handleClick( ),并且强制指定这个方法中的this指向当前的实例。

ES5的写法中为什么不用bind(this)

之前已经讲过,ES5的写法是指使用React.createClass( )方法来定义组件(自动绑定了this),React在V16以上的新版本中已经移除了这个API.

源码示意:

1
2
3
4
//旧版本`react`中`createClass`方法片段
if (this.__reactAutoBindMap) {
this._bindAutoBindMethods();
}

实际上在这个方法中所完成的,就是对组件中自定义方法的this强制绑定.

绑定this的必要性

在组件上绑定事件监听器,是为了响应用户的交互动作,特定的交互动作触发事件时,监听函数中往往都需要操作组件某个状态的值,进而对用户的点击行为提供响应反馈,对开发者来说,这个函数触发的时候,就需要能够拿到这个组件专属的状态合集(例如在上面的开关组件ToggleButton例子中,它的内部状态属性state.isToggleOn的值就标记了这个按钮应该显示ON或者OFF),所以此处强制绑定监听器函数的this指向当前实例的也很容易理解。

React构造方法中的bind会将响应函数与这个组件Component进行绑定以确保在这个处理函数中使用this时可以时刻指向这一组件的实例。

如果不绑定this

如果类定义中没有绑定this的指向,当用户的点击动作触发this.handleClick( )这个方法时,实际上执行的是原型方法,可这样看起来并没有什么影响,如果当前组件的构造器中初始化了state这个属性,那么原型方法执行时,this.state会直接获取实例的state属性,如果构造其中没有初始化state这个属性(比如React中的UI组件),说明组件没有自身状态,此时即使调用原型方法似乎也没什么影响。

事实上的确是这样,这里的bind(this)所希望提前规避的,就是著名的this指针丢失的问题

即如果没有强制指定组件实例方法的this,在将来的使用中就无法安心使用引用转换作为回调函数传递这样的方式,对于后续使用和协作开发而言都是不方便的。

案例

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import React from 'react';
import ReactDOM from 'react-dom';
import './Tab.css'


class Tab extends React.Component{
constructor(props){
super(props)

//设置状态、数据
this.state = {
c1:'content active',
c2:"content"
}

this.clickEvent = this.clickEvent.bind(this)//需要绑定this
}
clickEvent(e){ //e是默认事件对象;e.target会找到事件目标者
console.log('clickEvent')
console.log(e.target.dataset.index)//找到点击事件对象目标的dataset.*的属性值
let index = e.target.dataset.index;
console.log(this)
if(index=='1'){
this.setState({
c1:'content active'

})
this.setState({
c2:"content"
})
}else{
this.setState({
c1:'content',
c2:"content active"
})
}
}

render(){
return (
<div>
{/*data-* 来设置对象的dataset的属性值*/}
{/*此外,onclick中的this.clickEvent需要手动绑定this,因为使用的是ES6的class来生成类组件*/}
<button data-index="1" onClick={this.clickEvent}>内容一</button>
<button data-index="2" onClick={this.clickEvent}>内容二</button>
<div className={this.state.c1}>
<h1>内容1</h1>
</div>
<div className={this.state.c2}>
<h1>内容2</h1>
</div>
</div>
)
}
}



ReactDOM.render(
<Tab />,
document.querySelector('#root')
)