在React中,你必定会遇到的setState

只要使用React的小伙伴,必定接触到setState这个API,地位不言而喻react

但要使用好必定要抓住三个关键词:合并更新、同步异步、不可变值数组

咋一看,有些小伙伴可能云里雾里~性能优化

别急,我挨个解释markdown

1.setState会合并更新

  • 默认合并更新

先看一个例子🌰网络

import { Component } from  'react'

class App extends Component {
  state= {
      count: 1
  }
  handleAdd = () => {
      this.setState({
          count: this.state.count + 1
      })
      this.setState({
          count: this.state.count + 1
      })
      this.setState({
          count: this.state.count + 1
      })
      this.setState({
          count: this.state.count + 1
      })
  }
  render() {
    return (
        <div>
          { this.state.count }
          <button onClick={ this.handleAdd }>add</button>
        </div>
    )
  }
}

复制代码

这段代码,咱们重复的setState让count不断加1异步

可咱们会发现点击一次按钮仅加1函数

而不是咱们想一想加4post

其实就是React的底层优化性能

他会把多个setState中的更新的对象合并一次更新优化

底层相似使用 Object.assign 对更新对象进行合并处理

// 等价于 {  count: this.state.count + 1 }
   Object.assign({
      count: this.state.count + 1
    }, {
      count: this.state.count + 1
    }, {
      count: this.state.count + 1
    }, {
      count: this.state.count + 1
    })
复制代码

那如何避免不会被合并呢?

  • 函数不会被合并

可使用函数,函数不会被合并

// preState 更新以前的 state
// preProps 更新以前的 props
this.setState((preState, preProps) => {
	// return 此次要更新的对象
})
复制代码

来一个例子🌰

import { Component } from  'react'

class App extends Component {
  state= {
      count: 1
  }
  handleAdd = () => {
      this.setState((preState) => {
          console.log(preState) // {count: 1}
          return ({
              count: preState.count + 1
          })
      })
      this.setState((preState) => {
          console.log(preState) // {count: 2}
          return ({
              count: preState.count + 1
          })
      })
      this.setState((preState) => {
          console.log(preState) // {count: 3}
          return ({
              count: preState.count + 1
          })
      })
      this.setState((preState) => {
          console.log(preState) // {count: 4}
          return ({
              count: preState.count + 1
          })
      })
  }
  render() {
    return (
        <div>
          { this.state.count }
          <button onClick={ this.handleAdd }>add</button>
        </div>
    )
  }
}
复制代码

2.setState异步同步都是有可能的

  • 默认是异步

这是什么意思呢?

直接来个例子🌰

import { Component } from  'react'

class App extends Component {
    state= {
        count: 1
    }
    handleAdd = () => {
        this.setState({
            count: this.state.count + 1
        })
        console.log(this.state.count)
    }
    render() {
        return (
            <div>
                { this.state.count }
                <button onClick={ this.handleAdd }>add</button>
            </div>
        )
    }
}
复制代码

效果图:

看着效果图,小伙伴其实能够一目了然发现

console.log 以前其实已经 setState

但获取的值却仍是从 1 开始打印,而不是从 2 开始

也就是说 console.log 以前还未 setState

那此时 setState 即是异步更新

但咱们可使用 setState(updater, callback) 第二个参数(回调函数)

相似 Vue 中的 $nextTick

// 代码同上
// ...
handleAdd = () => {
    this.setState({
        count: this.state.count + 1
    }, () => {
        console.log(this.state.count) // 此时 count 等于2
    })
}
// ...
复制代码

固然你也能够在生命周期 componentDidUpdate 拿到所有完成更新的值

componentDidUpdate() {
	console.log(this.state.count) // 此时 count 等于2
}
复制代码

那什么时候同步?

  • 同步状况有:使用异步函数、自定义事件

异步函数能够是setTimeoutsetIntervalrequestAnimationFrame

class App extends Component {
    state= {
        count: 1
    }

    handleAdd = () => {
        setTimeout(() => {
            this.setState({
                count: this.state.count + 1
            })
            console.log(this.state.count) // 此时 count 等于2
        }, 0)
    }
    render() {
        return (
            <div>
                { this.state.count }
                <button onClick={this.handleAdd}>add</button>
            </div>
        )
    }
}
复制代码

效果图:

从代码和上面的图,咱们很容易看出

咱们仍是在 console.log 以前 setState

可是打印是从 2 开始的

那就能够说明此时setState同步更新的

相似自定义事件,附上例子🌰

import { Component } from  'react'

class App extends Component {
    state= {
        count: 1
    }
    componentDidMount() {
    	// 自定义事件
        this.handleAdd = event => {
            this.setState({
                count: this.state.count + 1
            })
            console.log(this.state.count) // 此时 count 等于2
        }
        const button = document.getElementById('button')
        button.addEventListener('click', this.handleAdd);
    }
    componentWillUnmount() {
    	// 移除自定义事件,防止内存泄漏
        const button = document.getElementById('button')
        button.removeEventListener('click', this.handleAdd);
    }

    render() {
        return (
            <div>
                { this.state.count }
                <button id='button'>add</button>
            </div>
        )
    }
}
复制代码
  • 同步或异步的原理

一张很经典的图

(图源于网络)

这图什么意思呢?

其实说白就是 React 用 isBatchingUpdates 去判断 setState 后该如何处理

咱们把点击事件拿出举例🌰

React 会在 setState 以前,isBatchingUpdates = true

到结束时才会让,isBatchingUpdates = false

此时,命中图中左侧判断:保存组件于 dirtyComponents 中

handleAdd = () => {
    // 开始时处于BathingUpdates, isBatchingUpdates = true
    
    this.setState({
        count: this.state.count + 1
    })
    
    console.log(this.state.count) // 此时 count 等于 1
    
    // 结束时 isBatchingUpdates = false
}   
复制代码

而同步时呢?

一样,setState 以前,isBatchingUpdates = true

结束时,isBatchingUpdates = false

此时,isBatchingUpdates = false 才 setState 开始工做

命中图中的右侧的判断,立刻开始一系列的更新数值

handleAdd = () => {
 	// ①开始时处于BathingUpdates, isBatchingUpdates = true
    
    setTimeout(() => {
        this.setState({
            count: this.state.count + 1
        })
        
        console.log(this.state.count) // ③此时 count 等于2
        
    }, 0)
    
   // ②结束时 isBatchingUpdates = false
}
复制代码

3.setState最好使用不可变值

使用不可变值,其实就是为了页面的性能优化

咱们知道其实有些页面组件没有更新的必要

例如:父组件更新,子组件也会随着更新

但子组件其实没有数值的更新,因此不必更新渲染

咱们可使用 shouldComponentUpdate 手动控制更新渲染

shouldComponentUpdate(nextProps, nextState) {
	//...
    // 若是 return false 表明组件不更新渲染
    // 若是 return true 表明组件更新渲染
}
复制代码

因此,咱们就要用不可变值,两个先后不同的props或是state对象进行比较

例如

// ...
// 传入数组list 
shouldComponentUpdate(nextProps, nextState) {
    // 引入 lodash 的 isEqual 比较数组值是否相等
    if (isEqual(nextProps.list,this.props.list)) {
        return false // 组件不更新
    }
    return false // 组件不更新
}
复制代码

若是你不使用不可变值,那指向有多是同一个对象

那么即时你使用shouldComponentUpdate,也会更新渲染

由于他们都是同一个对象

若是小伙伴不太明白~

那就看看我以前写的文章 如何理解react中的setState必定要用不可变值?说的很详细~

感谢阅读~

相关文章
相关标签/搜索