第一次真正遇到这个问题,还是在写react的时候,大致问题就是,我在子组件中改变了从父组件传进来的props,恰好这个props是一个位于父组件state上的object,然后再调用从父组件中传进来的props中的函数去试图更新父组件的props,然而父组件并没有重新render

我们都知道在react中,要刷新一个组件,也就是触发render必须要是state或者props的改变才可以,先抛开props不管,一般情况下,改变sate就会触发render重新渲染,那么问题就来了

import React, {PureComponent} from 'react';

export default class Test extends PureComponent {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}

clickButton = () => {
console.log('touched',this.state.count);
this.state.count += 1;
// this.setState({
// count: this.state.count + 1
// });
};

render() {
const {count} = this.state;
return (<div>
<p>{`已经点击${count}次`}</p>
<button onClick={this.clickButton}>
点击我测试
</button>
</div>);
}
}

我写一个按钮,点击一次触发clickButton,如果直接改变state的值,那么会出现这种情况

我点击了多次,state已经改变了,但是render并没有触发

其实raect的state是一个队列机制,react并不是每次setState,都会去执行真正的更新,react会将每一次setState放进队列,即进行state合并,完成后,再最后一起执行。

所以这里正确的写法应该是这样的

clickButton = () => {
console.log('touched',this.state.count);
let t = this.state.count+1;
// this.state.count += 1;
this.setState({
count: t
});
};

执行结果如下

但是,到这里还没有完,并不是说,我们换一种写法,能用就行就完事儿了,当我把代码再改变一下

clickButton = () => {
console.log('touched',this.state.count);
let t = this.state.count+1;
this.state.count += 1;
this.setState({
count: t
});
};

在setState之前,我先把count的值直接改变了,执行的结果如下

即使setState,还是没有重新render,那么这是什么原因呢

react不止对virtual dom有diff,对state依然有diff,diff就是每次更新状态前,去比对数据,如果不一样react才会重新渲染

所以当我在setState前直接改变state的值是同步的,改变之后,setState去执行异步更新时,发现这两个值是一样的,所以不会触发render重新渲染

为了印证是不是diff的结果造成的不会render,我们将代码再改变一下

clickButton = () => {
console.log('touched',this.state.count);
let t = this.state.count+1;
this.state.count += 1;
this.setState({
count: t+1
});
};

执行结果如下

很显然,setState的count值和直接设置的count值不一样,diif后,react就重新render渲染了页面

  • 补充说明

react setSate是一个异步操作,所以在setSate之后,想要直接this.state.XXX获取最新的state是不可行的,因注意避免

  • 2019.3.20补充:this.setState()虽然是一个异步操作,但其实能通过一些手段拿到最新的state

1、通过回调函数

this.setState({},()=>{console.log(this.state)})

2、在生命周期componentDidUpdate同样可以拿到最新state

3、用setTimeout()包裹一下

触发顺序如图

以raect state为例,探索JavaScript对象的深拷贝(下)

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注