【不止前端】 React基础入门1

⏰序言

对于刚学习 react 的小伙伴来说,总是从基础开始学习,周一自然也不例外捏。那在下面的文章中,将讲解 react 的基本使用和高级特性,更有周边插件 ReduxReact-router 待你来探寻。

在本文中,融合大量案例🌰和动图🕹️进行展示。可以把它当成是 react 的入门宝库,有不懂的语法知识点时或许在这里可以寻找到你的答案并且通过例子运用起来。

叮,废话不多说,下面来开始探索 react 的奥秘吧👏

📝一、React的基本使用

1、JSX基本使用

(1)变量、表达式

react 中,最基础的内容便是变量和表达式,具体形式如下:

第一种类型:获取变量、插值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 import React from 'react'

class JSXBaseDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '掘金:星期一研究室',
imgUrl: 'https://p3-passport.byteacctimg.com/img/user-avatar/cc88f43a329099d65898aff670ea1171~300x300.image',
flag: true
}
}
render() {
// 获取变量 插值
const pElem = <p>{this.state.name}</p>
return pElem
}
}

export default JSXBaseDemo

注意, react 中插值的形式是单个花括号 {} 的形式。最终浏览器显示的效果如下:

获取变量、插值

第二种类型:表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react'

class JSXBaseDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '掘金:星期一研究室',
imgUrl: 'https://p3-passport.byteacctimg.com/img/user-avatar/cc88f43a329099d65898aff670ea1171~300x300.image',
flag: true
}
}
render() {
// 表达式
const exprElem = <p>{this.state.flag ? 'yes' : 'no'}</p>
return exprElem
}
}

export default JSXBaseDemo

react 中也支持直接在插值里面使用表达式,如上述代码中的 this.state.flag ? 'yes' : 'no' 。最终浏览器的显示效果如下:

在这里插入图片描述

(2)class和style

通常情况下,如果我们要给某一个标签设置类名,那么会给该标签加上一个 class 。而在 react 中,如果想要给一个标签加上一个类,那么需要给其加上 className具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react'
import './style.css'
import List from '../List'

class JSXBaseDemo extends React.Component {
render() {
// class
const classElem = <p className="title">设置 css class</p>

// style
const styleData = { fontSize: '30px', color: 'blue' }
const styleElem1 = <p style={styleData}>设置 style</p>
// 内联写法,注意 {{ 和 }}
const styleElem2 = <p style={{ fontSize: '30px', color: 'blue' }}>设置 style</p>

// 返回结果
return [ classElem, styleElem1, styleElem2 ]
}
}

export default JSXBaseDemo

此时浏览器的显示效果为:

class和style

同时需要注意的是,在 react 中,如果要在标签里面写内联样式,那么需要使用双花括号 {{}} 来表示。

(3)子元素和组件

第一种类型:子元素

对于子元素来说,它可以在标签里面进行使用。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
js复制代码import React from 'react'

class JSXBaseDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '掘金:星期一研究室',
imgUrl: 'https://p3-passport.byteacctimg.com/img/user-avatar/cc88f43a329099d65898aff670ea1171~300x300.image',
flag: true
}
}
render() {
// 子元素
const imgElem = <div>
<p>我的头像</p>
<img src="xxxx.png"/>
<img src={this.state.imgUrl}/>
</div>
return imgElem
}
}

export default JSXBaseDemo

最终,浏览器的显示效果为:

子元素

第二种类型:加载组件

如果要在 React 中加载一个组件,那么我们可以这么处理。具体代码如下:

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
import React from 'react'
import './style.css'
import List from '../List'

class JSXBaseDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '掘金:星期一研究室',
imgUrl: 'https://p3-passport.byteacctimg.com/img/user-avatar/cc88f43a329099d65898aff670ea1171~300x300.image',
flag: true
}
}
render() {
// 加载组件
const componentElem = <div>
<p>JSX 中加载一个组件</p>
<hr/>
<List/>
</div>
return componentElem
}
}

export default JSXBaseDemo

List.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
34
js复制代码import React from 'react'

class List extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'React',
list: ['a', 'b', 'c']
}
}
render() {
return <div>
<p onClick={this.changeName.bind(this)}>{this.state.name}</p>
<ul>{
this.state.list.map((item, index) => {
return <li key={index}>{item}</li>
})
}</ul>
<button onClick={this.addItem.bind(this)}>添加一项</button>
</div>
}
changeName() {
this.setState({
name: '星期一研究室'
})
}
addItem() {
this.setState({
list: this.state.list.concat(`${Date.now()}`) // 使用不可变值
})
}
}

export default List

此时浏览器的显示效果如下:

子元素和组件

由此,我们就将一个组件注入到组件当中。

(4)原生 html

继续,我们来看原生 htmlreact 中是如何使用的。先看以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react'

class JSXBaseDemo extends React.Component {
render() {
// 原生 html
const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>'
const rawHtmlData = {
// 把 rawHtml 赋值给 __html
__html: rawHtml // 注意,必须是这种格式
}
const rawHtmlElem = <div>
<p dangerouslySetInnerHTML={rawHtmlData}></p>
<p>{rawHtml}</p>
</div>
return rawHtmlElem
}
}

export default JSXBaseDemo

此时浏览器的显示效果如下:

原生html

大家可以看到,如果要在 react 中使用原生 html ,那么必须使用 const rawHtmlData = { __html: rawHtml } 这种形式,才能将原生 html 代码给解析出来。否则的话, react 是无法正常将原生 html 解析出来的。

2、条件判断

(1)if else

先看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react'
import './style.css'

class ConditionDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'black'
}
}
render() {
const blackBtn = <button className="btn-black">black btn</button>
const whiteBtn = <button className="btn-white">white btn</button>

// if else
if (this.state.theme === 'black') {
return blackBtn
} else {
return whiteBtn
}
}
}

export default ConditionDemo

style.css 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
.title {
font-size: 30px;
color: red;
}

.btn-white {
color: #333;
}
.btn-black {
background-color: #666;
color: #fff;;
}

此时浏览器的显示效果为:

if else

大家可以看到,当我们 theme 设置为 black 时,最终显示的效果就是黑色。如果我们把 theme 设置为其他状态,那么最终显示的效果就是白色

(2)三元表达式

先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react'
import './style.css'

class ConditionDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'black'
}
}
render() {

const blackBtn = <button className="btn-black">black btn</button>
const whiteBtn = <button className="btn-white">white btn</button>

// 三元运算符
return <div>
{ this.state.theme === 'black' ? blackBtn : whiteBtn }
</div>
}
}

export default ConditionDemo

此时浏览器的显示效果为:

三元表达式

大家可以看到,我们也可以通过三元表达式 this.state.theme === 'black' ? blackBtn : whiteBtn 的方式来对一些条件进行判断。

(3)逻辑运算符 && ||

先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react'
import './style.css'

class ConditionDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'black'
}
}
render() {

const blackBtn = <button className="btn-black">black btn</button>
const whiteBtn = <button className="btn-white">white btn</button>

// &&
return <div>
{ this.state.theme === 'black' && blackBtn }
</div>
}
}

export default ConditionDemo

此时浏览器的显示结果也是和上述一样的。具体如下:

逻辑运算符

this.state.theme === 'black' && blackBtn 这句话的意思为,如果 this.state.theme 的值为 black 时,那么返回 backBtn 的结果。

3、渲染列表

(1)map 和 key

先来看一段代码:

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
import React from 'react'

class ListDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
}
}
render() {
return <ul>
{ /* 类似于vue中的v-for */
this.state.list.map(
(item, index) => {
// 这里的 key 和 Vue 的 key 类似,必填,不能是 index 或 random
return <li key={item.id}>
index {index}; id {item.id}; title {item.title}
</li>
}
)
}
</ul>
}
}

export default ListDemo

此时浏览器的显示效果如下:

渲染列表

react 中,使用的是 list.map 来渲染列表。其中, 需要注意的是, list.map() 是一个函数,那现在我们假设这个函数里面要遵循一套规则 list.map( item => item.id )

这个时候,我们视 .map 为一个数组的重组,那么重组的规则就是 item => item.id 。同时, list.map 返回的是一个数组

4、React的事件

(1)bind 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
import React from 'react'

class EventDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'zhangsan'
}

// 修改方法的 this 指向
// 使用这个写法,组件初始化时,只执行一次bind
this.clickHandler1 = this.clickHandler1.bind(this)
}
render() {
// this - 使用 bind
return <p onClick={this.clickHandler1}>
{this.state.name}
</p>
}
clickHandler1() {
// console.log('this....', this) // this 默认是 undefined
this.setState({
name: 'lisi'
})
}
}

export default EventDemo

最终浏览器显示的效果为:

bind this(1

在这段代码中,我们通过 this.clickHandler1 = this.clickHandler1.bind(this) 来对 clickHandler1 进行绑定。


还有另外一种关于 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
import React from 'react'

class EventDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'zhangsan'
}
}
render() {
// this - 使用静态方法
return <p onClick={this.clickHandler2}>
clickHandler2 {this.state.name}
</p>
}
// 静态方法,this 指向当前实例
clickHandler2 = () => {
this.setState({
name: 'lisi'
})
}
}

export default EventDemo

此时浏览器的显示效果为:

bind this

对于上面的这种方式来说, clickHandler2 是一个静态方法,此时它的 this 会指向当前的实例。

(2)关于 event 参数

先来看一段代码:

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
import React from 'react'

class EventDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'zhangsan'
}
}
render() {
// event
return <a href="https://imooc.com/" onClick={this.clickHandler3}>
click me
</a>
}
// 获取 event (重点)
clickHandler3 = (event) => {
event.preventDefault() // 阻止默认行为
event.stopPropagation() // 阻止冒泡
console.log('target', event.target) // 指向当前元素,即当前元素触发
console.log('current target', event.currentTarget) // 指向当前元素,假象!!!

// 注意,event 其实是 React 封装的。可以把 __proto__.constructor 看成是 SyntheticEvent 组合事件
console.log('event', event) // 不是原生的 Event ,原生的是 MouseEvent
console.log('event.__proto__.constructor', event.__proto__.constructor)

// 原生 event 如下。其 __proto__.constructor 是 MouseEvent
console.log('nativeEvent', event.nativeEvent)
console.log('nativeEvent target', event.nativeEvent.target) // 指向当前元素,即当前元素触发
console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 document !!!

}
}

export default EventDemo

此时浏览器的显示效果如下:

event

依据以上内容,需要注意的点是:

  • event 是合成事件 SyntheticEvent ,它能够模拟出来 DOM 事件所有的能力;
  • eventReact 封装出来的,而 event.nativeEvent 是原生事件对象;
  • 所有的事件,都会被挂载到 document 上;
  • React 中的事件,和 DOM 事件不一样,和 Vue 事件也不一样。

(3)传递自定义参数

先来看一段代码:

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
import React from 'react'

class EventDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'zhangsan',
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
}
}
render() {
// 传递参数 - 用 bind(this, a, b)
return <ul>{this.state.list.map((item, index) => {
return <li key={item.id} onClick={this.clickHandler4.bind(this, item.id, item.title)}>
index {index}; title {item.title}
</li>
})}</ul>
}
// 传递参数
clickHandler4(id, title, event) {
console.log(id, title)
console.log('event', event) // 最后追加一个参数,即可接收 event
}
}

export default EventDemo

此时,浏览器的显示效果为:

传递参数

大家可以看到,我们通过使用 this.clickHandler4.bind(this, item.id, item.title) 这种形式来对 react 中的事件进行参数传递。

(4)注意点

  • React 16 将事件绑定到 document 上;
  • React 17 将事件绑定到 root 组件上;
  • 这样做的好处在于:有利于多个 React 版本并存,例如微前端

如下图所示:

react16和17中事件的不同

5、表单

(1)input textarea select 用 value

先来看一段代码:

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
import React from 'react'

class FormDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '星期一研究室',
info: '个人信息',
city: 'GuangDong',
flag: true,
gender: 'female'
}
}
render() {

// 受控组件
return <div>
<p>{this.state.name}</p>
<label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for */}
<input id="inputName" value={this.state.name} onChange={this.onInputChange}/>
</div>
}
onInputChange = (e) => {
this.setState({
name: e.target.value
})
}
}

export default FormDemo

此时浏览器的显示效果如下:

受控组件

react 中,通过使用 onChange 事件来手动修改 state 里面的值。


上面我们已经讲解了 input ,接下来我们来看 textareaselect

先来看 textarea 相关的代码:

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
import React from 'react'

class FormDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '星期一研究室',
info: '个人信息',
city: 'GuangDong',
flag: true,
gender: 'female'
}
}
render() {

// textarea - 使用 value
return <div>
<textarea value={this.state.info} onChange={this.onTextareaChange}/>
<p>{this.state.info}</p>
</div>
}
onTextareaChange = (e) => {
this.setState({
info: e.target.value
})
}
}

export default FormDemo

此时浏览器的打印效果是:

textarea

同样地, textarea 也是用 valueonChange 来对进行绑定。


继续来看 select具体代码如下:

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
import React from 'react'

class FormDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '星期一研究室',
info: '个人信息',
city: 'GuangDong',
flag: true,
gender: 'female'
}
}
render() {

// select - 使用 value
return <div>
<select value={this.state.city} onChange={this.onSelectChange}>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>
<p>{this.state.city}</p>
</div>
}
onSelectChange = (e) => {
this.setState({
city: e.target.value
})
}
}

export default FormDemo

此时,浏览器的显示效果为:

select

inputtextarea 一样,也是通过操作 valueonChange ,来改变最终的值。

(3)checkbox radio 用 checked

先来看 ckeckbox代码如下:

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
import React from 'react'

class FormDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '星期一研究室',
info: '个人信息',
city: 'GuangDong',
flag: true,
gender: 'female'
}
}
render() {

// checkbox
return <div>
<input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/>
<p>{this.state.flag.toString()}</p>
</div>
}
onCheckboxChange = () => {
this.setState({
flag: !this.state.flag
})
}
}

export default FormDemo

此时浏览器的显示效果为:

checkbox

在上面的代码中, checkbox 通过操作 checkedonChange ,来改变 state 的值。


radio 也是类似,如下代码所示:

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
import React from 'react'

class FormDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '星期一研究室',
info: '个人信息',
city: 'GuangDong',
flag: true,
gender: 'female'
}
}
render() {

// radio
return <div>
male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/>
female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/>
<p>{this.state.gender}</p>
</div>
}
onRadioChange = (e) => {
this.setState({
gender: e.target.value
})
}
}

export default FormDemo

此时浏览器的显示效果是:

radio

6、组件使用

对于父子组件的使用来说,我们需要明白三个知识点:props 传递数据、props 传递函数和 props 类型检查。

先来看一段代码:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import React from 'react'
import PropTypes from 'prop-types'

class Input extends React.Component {
constructor(props) {
super(props)
this.state = {
title: ''
}
}
render() {
return <div>
<input value={this.state.title} onChange={this.onTitleChange}/>
<button onClick={this.onSubmit}>提交</button>
</div>
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
})
}
onSubmit = () => {
const { submitTitle } = this.props
submitTitle(this.state.title)

this.setState({
title: ''
})
}
}
// props 类型检查
Input.propTypes = {
submitTitle: PropTypes.func.isRequired
}

class List extends React.Component {
constructor(props) {
super(props)
}
render() {
const { list } = this.props

return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>
}
}
// props 类型检查
// 通过propTypes可以清楚地知道list需要一个什么类型的数据
List.propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired
}

class Footer extends React.Component {
constructor(props) {
super(props)
}
render() {
return <p>
{this.props.text}
{this.props.length}
</p>
}
componentDidUpdate() {
console.log('footer did update')
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.text !== this.props.text
|| nextProps.length !== this.props.length) {
return true // 可以渲染
}
return false // 不重复渲染
}

// React 默认:父组件有更新,子组件则无条件也更新!!!
// 性能优化对于 React 更加重要!
// SCU 一定要每次都用吗?—— 需要的时候才优化(SCU即shouldComponentUpdate)
}

// 父组件
class TodoListDemo extends React.Component {
constructor(props) {
super(props)
// 状态(数据)提升
// list的数据需要放在父组件
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
],
footerInfo: '底部文字'
}
}
render() {
return <div>
<Input submitTitle={this.onSubmitTitle}/>
<List list={this.state.list}/>
<Footer text={this.state.footerInfo} length={this.state.list.length}/>
</div>
}
onSubmitTitle = (title) => {
this.setState({
list: this.state.list.concat({
id: `id-${Date.now()}`,
title
})
})
}
}

export default TodoListDemo

此时浏览器的显示效果如下:

组件使用-props

依据以上代码,我们来对 props 的各个类型进行介绍。

(1)props 传递数据

最后一个 TodoListDemo 是父组件,其他都是子组件。在 Input 组件和 List 组件中,我们将 props 属性的内容,以 this.props.xxx 的方式,传递给父组件

(2)props 传递函数

React 在传递函数这一部分和 vue 是不一样的。对于 vue 来说,如果有一个父组件要传递函数给子组件,子组件如果想要触发这个函数,那么需要使用事件传递和 $emit 的方式来解决。

大家定位到 Input 组件中,在这里,我们将 submitTitle 以函数的形式,传递给父组件中的 onSubmitTitle

(3)props 类型检查

大家定位到两处 props 类型检查的地方。使用 react 中的 PropTypes ,我们可以对当前所使用的属性进行一个类型检查。比如说: submitTitle: PropTypes.func.isRequired 表明的是, submitTitle 是一个函数,并且是一个必填项。

就这样,通过上面的例子,我们学习了属性传递属性验证以及父组件和子组件之间怎么通过传事件的形式来进行通信

7、setState

(1)不可变值

所谓不可变值,即所设置的值永不改变。那这个时候,我们就需要去创建一个副本,来设置 state 的值。

来看几个要点:

第一点:state 要在构造函数中定义。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react'

// 函数组件,默认没有 state
class StateDemo extends React.Component {
constructor(props) {
super(props)

// 第一,state 要在构造函数中定义
this.state = {
count: 0
}
}
render() {
return <div>
<p>{this.state.count}</p>
</div>
}
}

export default StateDemo

第二点,不要直接修改 state ,要使用不可变值。如下代码所示:

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
js复制代码import React from 'react'

// 函数组件,默认没有 state
class StateDemo extends React.Component {
constructor(props) {
super(props)

// 第一,state 要在构造函数中定义
this.state = {
count: 0
}
}
render() {
return <div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
}
increase= () => {
// 第二,不要直接修改 state,使用不可变值
// this.state.count++ // 错误写法,会直接修改原来的值
this.setState({
count: this.state.count + 1 // ShouldComponentUpdate → SCU
})
}
}

export default StateDemo

大家可以看到,在上面的代码中,我们通过 this.state({}) 这种形式,来修改 state 的值。值得注意的是,很多小伙伴会直接使用 this.state.count++ 来修改 state 的值,这在 react 中是非常不允许的。因此,要注意这个要点。

第三点,在 react 中操作数组的值。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
// 不可变值(函数式编程,纯函数) - 数组
const list5Copy = this.state.list5.slice()
list5Copy.splice(2, 0, 'a') // 中间插入/删除
this.setState({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3), // 截取
list4: this.state.list4.filter(item => item > 100), // 筛选
list5: list5Copy // 其他操作
})
// 注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值

第四点,在 react 中操作对象的值。如下代码所示:

1
2
3
4
5
6
js复制代码// 不可变值 - 对象
this.setState({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
})
// 注意,不能直接对 this.state.obj 进行属性设置,即 this.state.obj.xxx 这样的形式,这种形式会违反不可变值

(2)可能是异步更新

react 中的 state ,有可能是异步更新。来看一段代码:

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
import React from 'react'

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

this.state = {
count: 0
}
}
render() {
return <div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
}
increase= () => {
// setState 可能是异步更新(也有可能是同步更新)
this.setState({
count: this.state.count + 1
}, () => {
// 联想 Vue $nextTick - DOM
console.log('count by callback', this.state.count) // 回调函数中可以拿到最新的 state
})
console.log('count', this.state.count) // 异步的,拿不到最新值
}
}

export default StateDemo

此时浏览器的显示效果为:

异步更新①

大家可以看到,this.state 前半部分并不能同一时间得到更新,所以它是异步操作。而后面的箭头函数中的内容可以得到同步更新,所以后面函数的部分是同步操作


值得注意的是, setTimeoutsetState 中是同步的来看一段代码:

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
import React from 'react'

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

this.state = {
count: 0
}
}
render() {
return <div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
}
increase= () => {
// setTimeout 中 setState 是同步的
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log('count in setTimeout', this.state.count)
}, 0)
}
}

export default StateDemo

此时,浏览器的显示效果为:

异步更新②


还有一个要注意的点是,如果是自己定义的 DOM 事件,那么在 setState 中是同步的,用在 componentDidMount 中。

如果是销毁事件,那么用在 componentWillMount 生命周期中。代码如下:

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
import React from 'react'

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

this.state = {
count: 0
}
}
render() {
return <div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
}
bodyClickHandler = () => {
this.setState({
count: this.state.count + 1
})
console.log('count in body event', this.state.count)
}
componentDidMount() {
// 自己定义的 DOM 事件,setState 是同步的
document.body.addEventListener('click', this.bodyClickHandler)
}
componentWillUnmount() {
// 及时销毁自定义 DOM 事件
document.body.removeEventListener('click', this.bodyClickHandler)
// clearTimeout
}
}

export default StateDemo

此时浏览器的显示效果为:

异步更新③

(3)可能会被合并

setState 在传入对象时,更新前会被合并。来看一段代码:

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
import React from 'react'

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

this.state = {
count: 0
}
}
render() {
return <div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
}
increase= () => {
// 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
}
}

export default StateDemo

此时浏览器的显示效果为:

传入对象,会被合并

有小伙伴可能会觉得,一下子多个三个 setState ,那结果应该是 +3 才是。但其实,如果传入的是对象,那么结果会把三个合并为一个,最终只执行一次


还有另外一种情况,如果传入的是数,那么结果不会被合并。来看一段代码:

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
import React from 'react'

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

this.state = {
count: 0
}
}
render() {
return <div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
}
increase= () => {
// 传入函数,不会被合并。执行结果是 +3
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
}
}

export default StateDemo

此时浏览器的显示效果为:

传入函数,结果不会被合并

大家可以看到,如果传入的是函数,那么结果一下子就执行三次了。

8、组件生命周期

react 的组件生命周期,有单组件生命周期父子组件生命周期。其中,父子组件生命周期Vue 类似。

这里附上一个生命周期相关的网站:projects.wojtekmaj.pl/react-lifec…

下面附上生命周期的图:

react的生命周期

转至:React快速入门,一文弄懂react的基本使用和高级特性 - 掘金 (juejin.cn)