为什么需要setState方法

​ 首先我们需要知道,React不同于Vue,他并没有实现Vue的响应式原理,我们需要手动的调用setState方法修改数据通知React进行更新界面

setState方法数据的合并

首先需要知道,setState在执行的时候,并不是覆盖原来的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
import React, { Component } from "react";

export default class App extends Component {
constructor(props) {
super(props);
this.state = {
name:'coderwei',
message: "13",
};
}
change() {
this.setState({
message: "hello",
});
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<h2>{this.state.name}</h2>
<button onClick={this.change.bind(this)}>改变state</button>
</div>
);
}
}
//我们会发现,name属性并没有消失,他依旧显示在界面上,所以在我们在使用setState的时候并不需要手动的进行一次拷贝,然后在将整个对象放到setState里面

setState方法是同步的还是异步的

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
import React, { Component } from "react";

export default class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "13",
};
}
change() {
this.setState({
message: "hello",
});
console.log(this.state.message)//输出的是13
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.change.bind(this)}>改变state</button>
</div>
);
}
}

上述场景就可以证明setState是一个异步方法,明明我们在输出之前就已经修改了message的代码,但是输出的依旧是修改之前的值

为什么setState是异步的

​ 明确他是异步的之后,我们就需要知道为什么?了解真相才能获得真正的自由,首先贴一个链接,redux作者 Dan Abramov对这个方法是同步还是异步的回应,大概在四分之一的位置。

  1. 首先,如果setState方法不是异步的,会导致一个很明显的问题,那就是render函数会频繁的会调用,这样子效率是非常低下的,所以react内部将多个setState维护到一个队列中,然后一起进行更新,在其中如果key相同的则会进行覆盖,这样就大大的提升了性能。
  2. 其次,如果setState是同步的,那么会出现一个问题,也是上面redux作者回应举的一个例子,当setState执行完毕之后,可能App组件的render函数还没有调用完,如果里面用到了其他组件并且其他组件依赖message,那么就会导致state和props的数据不一致,这在开发中可能会导致出现很多问题

在setState异步的情况下,如何获取跟新后的数据

​ 如果我们需要获取setState更新后的数据,那么setState的第二个参数就派上用场了,setState第二个参数是一个回调函数,当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
import React, { Component } from "react";

export default class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "13",
};
}
change() {
this.setState(
{
message: "hello",
},
() => {
console.log("state的回调:", this.state.message); //hello
}
);
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.change.bind(this)}>改变state</button>
</div>
);
}
}

同步的setState

当我们将setState丢在一个定时器内部,这个时候setState就会变成一段同步代码,因为当代码块执行到setTimeout的时候,并没有去执行,而是将他丢到一个任务队列里。

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, { Component } from "react";

export default class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "13",
};
}
change() {
setTimeout(() => {
this.setState(
{
message: "hello",
},
() => {
console.log("state的回调:", this.state.message); //hello
}
);
console.log("方法里面的输出", this.state.message); //hello
}, 0);
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.change.bind(this)}>改变state</button>
</div>
);
}
}

不建议直接修改state的数据

​ 在react里,不建议直接修改state的值,官方称 不可变的力量,如果我们书写代码都是继承自Component,那么其实修改也没有任何问题,不建议修改state的值,是为了考虑到性能优化,我们在父组件修改了一个属性,父组件重新渲染,也会导致子组件的内容重新渲染,这并不是我们希望看到的,你父组件修改个属性,跟我子组件有什么关系,我又不依赖你修改的这个属性,我是保持不变的,重新渲染就太浪费性能了,于是shouldComponentUpdate这个生命周期就派上用场了

​ shouldComponentUpdate是控制组件是否执行render函数的,他默认返回true,就是无论什么情况下都执行render函数,于是我们就可以在这上面下手,我们可以判断,我子组件的props 是否和父组件的state保持一致,保持一致的话就代表数据没有发生变化,我就不需要执行render刷新界面。shouldComponentUpdate在实际开发中用的还是蛮频繁的。文字总是苍白无力的,还是用代码来表述。

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, { Component } from "react";

export default class App extends Component {
constructor(props) {
super(props);
this.state = {
arr: [1, 2, 3, 4, 5],
};
}
change = () => {
this.state.arr.push(6);
this.setState({
arr: this.state.arr,
});
};
shouldComponentUpdate(newProps, newState) {
console.log(newState); //[1,2,3,4,5,6]
console.log(this.state.arr);//[1,2,3,4,5,6]
return false;
}
render() {
return (
<div>
{this.state.arr.map(item => {
return <li key={item}>{item}</li>;
})}
<button onClick={this.change}>=1</button>
</div>
);
}
}

​ 每次点击按钮,最新的state和当前组件实例对象的state是一模一样的,因为javascript中复杂数据类型保存的是内存地址,也就是说他们保存的都是一个对象的引用,最后他们指向的都是同一个对象,于是他们就永远保持一致,这样就没办法判断state有没有发生变化,因为我们需要通过state的变化来判断界面需不需要重新渲染。

​ 所以上面的代码我们稍微修改一下,不去直接修改state的值,而是创建一个新的arr数组,然后赋值给arr

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
import React, { Component } from "react";

export default class App extends Component {
constructor(props) {
super(props);
this.state = {
arr: [1, 2, 3, 4, 5],
};
}
change = () => {
let newArr = [...this.state.arr, 6];
this.setState({
arr: newArr,
});
};
shouldComponentUpdate(newProps, newState) {
console.log( newState); //[1,2,3,4,5,6]
console.log(this.state.arr); //[1,2,3,4,5]
return false;
}
render() {
return (
<div>
{this.state.arr.map(item => {
return <li key={item}>{item}</li>;
})}
<button onClick={this.change}>=1</button>
</div>
);
}
}

我们就可以在shouldComponentUpdate中判断,如果新的state和当前对象的state保持一致,那我们就不需要执行render函数渲染界面,这样就可以极大程度上避免毫无意义的render函数的执行