一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
前言
大家好呀,我是L同学。在上篇文章中,我们学习了setState的基本使用以及setState的异步更新。在这篇文章中,我们还继续介绍setState的更新,包括同步更新,数据合并等。
setState同步
在上篇文章中,我们讲到了setState的异步。那么setState一定是异步的吗?
我们来测试下。这个案例是刚开始显示文本hello world,点击按钮后通过setState更改数据,改为hello react。我们把setState放到setTimeout中进行更新。
import React, { Component } from 'react'
export default class App extends Component {
constructor() {
super()
this.state = {
message: 'hello world'
}
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={e => this.changeText()}>改变文本</button>
</div>
)
}
changeText() {
setTimeout(() => {
this.setState({
message: 'hello react'
})
console.log(this.state.message);
}, 0)
}
}
我们可以看到刚开始显示的是hello world。
当我点击按钮后,控制台打印出了hello react,页面也进行了更新。
我们再看另外一种情况,原生DOM事件。
同一个案例,增加一个按钮,同样地点击按钮2更新数据。
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={e => this.changeText()}>改变文本</button>
<button id='btn'>改变文本2</button>
</div>
)
}
componentDidMount() {
document.getElementById('btn').addEventListener('click', (e) => {
this.setState({
message: 'hello react'
})
console.log(this.state.message);
})
}
我们可以看到刚开始数据是hello world。点击按钮后获取到的数据是hello react,页面上数据也进行了更新。
我们来总结一下。setState不一定是异步的。分两种情况:
- 在组件生命周期或者react合成事件(如react中的onClick事件)中,setState是异步的。
- 在setTimeout或者原生DOM事件中,setState是同步的。
setState数据的合并
import React, {Component} from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
message: 'hello world',
name: 'haha'
}
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<h2>{this.state.name}</h2>
<button onClick={e => this.changeText()}>改变文本</button>
</div>
)
}
changeText() {
this.setState({
message: 'hello react'
})
}
}
我们点击按钮,改变了message的数据,但是不会对name产生影响的。
react源码中有对原对象和新对象进行合并的,做了以下操作。
Object.assign({}, this.state, {message: 'hello react'})
setState本身的合并
import React, {Component} from "react";
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 1
})
}
}
我们猜测下点击按钮counter会加3次变成3吗?
我们看到点击按钮一次,counter加了1,并没有加3。这是因为setState本身进行了合并。
那如果我们需要setState合并时进行累加,点击按钮counter变为3,该怎么做呢?
increment() {
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
})
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
})
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
})
}
我们可以看到点击按钮一次,counter变为了3。
组件嵌套的render调用
接下来我们做这样一个案例,各个组件之间的嵌套关系如图所示。在App组件中有一个+1按钮,可以让counter数据加1,并且会重新调用render函数。
import React, { Component } from 'react'
function Header() {
console.log('Header被调用');
return <h2>我是Header组件</h2>
}
class Banner extends Component {
render() {
console.log('Banner render函数被调用');
return <h3>我是Banner组件</h3>
}
}
function ProductList() {
console.log('ProductList被调用');
return (
<ul>
<li>商品列表1</li>
<li>商品列表2</li>
<li>商品列表3</li>
<li>商品列表4</li>
<li>商品列表5</li>
</ul>
)
}
class Main extends Component {
render() {
console.log('Main render函数被调用');
return (
<div>
<Banner/>
<ProductList/>
</div>
)
}
}
function Footer() {
console.log('Footer被调用');
return <h2>我是Footer组件</h2>
}
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
render() {
console.log('App render函数被调用');
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<Header/>
<Main/>
<Footer/>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
我们可以看到各个组件都被调用了,创建组件的时候就会调用render函数。
我们清空控制台,然后点击+1按钮。此时会重新调用App的render函数。而当App的render函数被调用时,所有的子组件的render函数都会被重新调用。
我们只更新了App组件中的数据,没有改变其他组件,但是它们都会被重新render,进行diff算法,这会造成性能低下。它们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法。
此时我们就可以通过shouldComponentUpdate方法来控制render的调用。shouldComponentUpdate方法我们在下篇文章中详细介绍。