setState 是如何给 state 赋值的

  • 通过​​Object.assign()​
import React from 'react';

class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'BNTang',
age: 18
}
let oldObj = {name: 'BNTang', age: 18};
let newObj = {age: 666};

let obj = Object.assign({}, oldObj, newObj);
console.log(obj);
}

render() {
return (
<div>
<p>{this.state.name}</p>
<p>{this.state.age}</p>
<button onClick={() => {
this.btnClick()
}}>按钮
</button>
</div>
)
}

btnClick() {
this.setState({
age: 666
});
}
}

class App extends React.Component {
render() {
return (
<div>
<Home/>
</div>
)
}
}

export default App;

React-组件-setState_回调函数

state 合并现象

  • 因为​​setState​​ 会收集一段时间内所有的修改操作,然后在统一的执行,再更新界面
  • 所以就出现了 state 的合并现象

首先来看一个案例,然后引出这个 state 的合并场景先如下:

import React from 'react';

class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
age: 0
}
}

render() {
return (
<div>
<p>{this.state.age}</p>
<button onClick={() => {
this.btnClick()
}}>按钮
</button>
</div>
)
}

btnClick() {
this.setState({
age: this.state.age + 1
});
this.setState({
age: this.state.age + 1
});
this.setState({
age: this.state.age + 1
});

console.log(this.state.age);
}
}

class App extends React.Component {
render() {
return (
<div>
<Home/>
</div>
)
}
}

export default App;

然后查看结果发现居然是 1:

React-组件-setState_解决方案_02

为什么最终的一个值是1, 不是 3 呢是吧,我明明是进行增加了 3 次加 1 的操作,因为 setState 默认是一个异步的方法, 默认会收集一段时间内所有的更新, 然后再统一更新, 所以就导致了最终的一个值是 1, 不是 3,博主可以大致的提供一下它底层的实现代码这样可以更加的让你对 setState 有更深层次的理解,如下:

let oldObj = {age: 0};
let stateList = [
// 演变过程1
// {age: oldObj.age + 1},
// {age: oldObj.age + 1},
// {age: oldObj.age + 1},

// 演变过程2
// {age: 0 + 1},
// {age: 0 + 1},
// {age: 0 + 1},

// 演变过程3
{age: 1},
{age: 1},
{age: 1}
];
stateList.forEach((newObj) => {
// 演变过程1
// Object.assign({}, {age: 0}, {age: 1}); // {age: 1}

// 演变过程2
// Object.assign({}, {age: 1}, {age: 1}); // {age: 1}

// 演变过程3
// Object.assign({}, {age: 1}, {age: 1}); // {age: 1}
oldObj = Object.assign({}, oldObj, newObj);
});

console.log(oldObj);

解决 state 合并现象

第一种方案就是前面所说的通过 setState 方法的第二个参数, 通过回调函数拿到更新之后的值,然后在根据该值在进行加一操作如下:

import React from 'react';

class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
age: 0
}
}

render() {
return (
<div>
<p>{this.state.age}</p>
<button onClick={() => {
this.btnClick()
}}>按钮
</button>
</div>
)
}

btnClick() {
this.setState({
age: this.state.age + 1
}, () => {
this.setState({
age: this.state.age + 1
}, () => {
this.setState({
age: this.state.age + 1
});
});
});
console.log(this.state.age);
}
}

class App extends React.Component {
render() {
return (
<div>
<Home/>
</div>
)
}
}

export default App;

但是上面的代码存在弊端,层级结构比较深,以后维护比较不友好,所以说 React 也考虑到了这一点,所以这里就引出了第二种解决方案,通过 setState 的第一个参数来进行解决,第一个参数除了可以传递一个对象,其实还可以传递一个回调函数,回调函数有两个默认的参数第一个就是上一次更新的最新的值,然后我们可以在该回调函数中就可以直接拿到最新的值,就不会出现合并的现象了。

import React from 'react';

class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
age: 0
}
}

render() {
return (
<div>
<p>{this.state.age}</p>
<button onClick={() => {
this.btnClick()
}}>按钮
</button>
</div>
)
}

btnClick() {
this.setState((preState, props) => {
return {age: preState.age + 1};
});
this.setState((preState, props) => {
return {age: preState.age + 1};
});
this.setState((preState, props) => {
return {age: preState.age + 1};
});
console.log(this.state.age);
}
}

class App extends React.Component {
render() {
return (
<div>
<Home/>
</div>
)
}
}

export default App;

React-组件-setState_React_03

那么为什么这样就可以解决从 0 变为 3 呢,这里也提供出它底层的实现,和演变过程,可以更深层次的加深理解:

let oldObj = {age: 0};

let stateList = [
(preState) => {
return {age: preState.age + 1}
},
(preState) => {
return {age: preState.age + 1}
},
(preState) => {
return {age: preState.age + 1}
},
];

stateList.forEach((fn) => {
// 演变过程1
// {age: 1}

// 演变过程2
// {agg: 2}

// 演变过程3
// {agg: 3}

let newObj = fn(oldObj);

// 演变过程1
// {age: 0} {age: 1} / {age: 1}

// 演变过程2
// {age: 1} {age: 2} / {age: 2}

// 演变过程3
// {age: 2} {age: 3} / {age: 3}
oldObj = Object.assign({}, oldObj, newObj);
});
console.log(oldObj);