React学习笔记——this.setState的基础使用和不同传参方法详解

前言

今天同事在开发过程中遇到了个问题,在使用AntD的Form组件时,内置的onFinish方法里面调用了2次setState方法,发现return函数渲染了2次,不过我记得多次调用setState时,会批量合并,所以就产生了一些疑惑,就上网查了一些资料,学习记录一下。

1、setState的使用

使用过React的应该都知道,在React中,一个组件中要读取当前状态需要访问this.state,但是更新状态却需要使用this.setState不是直接在this.state上修改
setState(updater, callback)这个方法是用来告诉react组件数据有更新,有可能需要重新渲染。
就比如这样:

//读取状态
const count = this.state.count;

//更新状态
this.setState({count: count + 1});
或
this.setState(preState=>({count:preState.count + 1}))

//无意义的修改
this.state.count = count + 1;

2、setState的同步和异步

在印象当中,setState是异步的,毕竟日常开发过程中,发现在使用setState改变状态之后,立刻通过this.state去拿最新的状态往往是拿不到的。

当时一步步查看资料发现,setState并不是简单的异步就完事了。

如果想详细看代码流程,可以看一下 博主:虹晨 的这篇博客,这里我就不写源码了。
( https://juejin.cn/post/6844903636749778958 ) 
(1)合成事件

所谓合成事件,就是react为了解决跨平台,兼容性等问题,自己封装了一套事件机制,代理了原生事件,想在jsx中比较常见的onClickonChange等,都是合成事件。

class App extends Component {

  state = { val: 0 }

  increment = () => {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val) // 输出的是更新前的val --> 0
  }
  render() {
    return (
      <div>
           {`Counter is: ${this.state.val}`}
           <button onClick={this.increment}>点击我</button>
      </div>
    )
  }
}

在这里插入图片描述
我们发现:

  • onClick合成事件中,val并没有在setState后面立即 + 1,在控制台中打印的仍是更改之前的值 0
结论:

合成事件中,setState是“异步”的

(2)生命周期(钩子函数)

componentDidMount为例

class App extends Component {

  state = { val: 0 }

 componentDidMount() {
    this.setState({ val: this.state.val + 1 })
   console.log(this.state.val) // 输出的还是更新前的值 --> 0
 }
  render() {
    return (
      <div>
        {`Counter is: ${this.state.val}`}
      </div>
    )
  }
}

在这里插入图片描述
我们发现:

  • 和合成事件一样,在生命周期里的setState,val并没有在setState后面立即 + 1,在控制台中打印的仍是更改之前的值 0
结论:

生命周期中,setState是“异步”的

(3)原生事件

所谓原生事件是指非react合成事件,例如原生自带的事件监听 addEventListener,或者也可以用原生js、jq直接 document.querySelector().onclick这种绑定事件的形式都属于原生事件。

class App extends Component {

  state = { val: 0 }

  changeValue = () => {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val) // 输出的是更新后的值 --> 1
  }

 componentDidMount() {
    document.body.addEventListener('click', this.changeValue, false)
 }
 
  render() {
    return (
      <div>
        {`Counter is: ${this.state.val}`}
      </div>
    )
  }
}

在这里插入图片描述
我们发现:

  • 原生事件中,我们监听click事件后,val在setState后面立即 + 1,在控制台中打印的是更改之后的值 1,2,3
结论:

原生事件中,setState是“同步”的

(4)异步中调用(setTimeout为例)

在 setTimeout 中去 setState 并不算是一个单独的场景,它是随着你外层去决定的,因为你可以在合成事件中 setTimeout ,可以在钩子函数中 setTimeout ,也可以在原生事件setTimeout。

这里我们在三种情况下都使用一下setTimeout,观察其不同的状态

class App extends Component {

  state = { val: 0 }

 componentDidMount() {
        setTimeout(_ => {
          this.setState({ val: this.state.val + 1 })
          console.log('1111',this.state.val) // 输出更新后的值 
        }, 6000)
        document.body.addEventListener('click', this.changeValue, false)
     }
    handleClick = () => {
        setTimeout(_ => {
            this.setState({ val: this.state.val + 1 })
            console.log('2222',this.state.val) // 输出更新后的值 
        }, 1000)
    }

    changeValue = () => {
        setTimeout(_ => {
            this.setState({ val: this.state.val + 1 })
            console.log('3333',this.state.val) // 输出的是更新后的值 
        },2000)
    }
    
    render() {
        return (
            <div>
            {`Counter is: ${this.state.val}`}
            <button onClick={this.handleClick}>点击我</button>
            </div>
        )
    }
}

在这里插入图片描述
我们发现:

  • 不管是在合成事件中 setTimeout ,或者在钩子函数中 setTimeout ,或者在原生事件的setTimeout,基于event loop的模型下, setTimeout 中里去 setState 总能拿到最新的state值
结论:

异步中的setState,会同步执行

(5)总结(源码中的try catch)

在相关源码里面,有一个try finally语法,注意这里不是try catch呦,说实话这个try finally这个语法我之前也没怎么用过,不过查阅资料后发现,这个挺好用的,言归正传。
try finally简单来说就是会先执行try代码块中的语句,然后再执行finally
中的代码。

  1. 合成事件生命周期中:是属于try代码块中的逻辑,而try里面有个return,所以你执行完setState后的state没有立即更新,console.log还是之前的state状态;这和个时候执行finally里面的代码,会先更新你的state,并且渲染到UI上面。导致setState表现为异步

  2. 原生事件中:没有被return,所以会直接更新。导致setState表现为同步

  3. 异步比如setTimeout中:当在try里面执行到setTimeout时,把它丢到任务队列,并没有执行,而是先执行finally里面代码块,等finally执行完成后,再到任务队列setState的时候,走的是和原生事件一样的分支逻辑。导致setState表现为同步

注意:

  • setState的“异步”不是说内部由异步代码实现,其实本身执行的过程代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”

结论:

  1. setState 只在合成事件钩子函数中是“异步”
  2. 原生事件setTimeout中都是同步

3、setState的参数

正常情况下,setState(arg1,arg2)括号内可以传递两个参数

(1)参数一:arg1

参数一arg1可以传两种形式,第一种是对象,第二种是函数

(1)对象式:
this.setState({ val : 1});

this.setState({ val : this.state.val + 1});
(2)函数式:

这个函数会接收到两个参数,第一个是当前的state,第二个是当前的props,这个函数应该返回一个对象,这个对象代表想要对this.state的更改。换句话说,之前你想给this.setState传递什么对象参数,在这种函数里就返回什么对象,不过,计算这个对象的方法有些改变,不再依赖于this.state,而是依赖于输入参数state。

这个函数格式是固定的,必须第一个参数是state的前一个状态,第二个参数是属性对象props,这两个对象setState会自动传递到函数中去

写法一

this.setState((preState, props) => {
                    return {val: props.val}
                });

写法二

this.setState((preState, props) => ({
                    isShow: !preState.isShow
                }));

注意:

 - 如果新状态不依赖于原状态--------使用对象方式  ( 对象方式是函数方式的简写方式 )
 - 如果新状态依赖于原状态 --------使用函数方式

有时你可以在return之前做些什么,比如

this.setState((preState,props)=>(
            console.log('111',this.state.val),
            {val:preState.val + 5}
        ))
//或者
this.setState((preState,props)=>{
            console.log('333',this.state.val)
            return {
                val : preState.val + 10
            }
        })

不过一般这种场景很少

(2)参数二:arg2

一个回调函数callBack,当setState结束并重新呈现该组件时将被调用。
如果需要在setState()后获取最新的状态数据, 在第二个callback函数中读取

this.setState({aa:1},()=>{
           console.log('是setState更新完,页面render完,再执行这个函数')
})

4、setState的批量更新

React的官方文档中有这么一句话:

状态更新会合并(也就是说多次setstate函数调用产生的效果会合并)

比如下面这种情况:

class Demo extends Component {
    state = { val: 0 }

	batchUpdates = () => {
	    this.setState({ val: this.state.val + 1 })
	     console.log('111',this.state.val)
	    this.setState({ val: this.state.val + 1 })
	     console.log('222',this.state.val)
	    this.setState({ val: this.state.val + 1 })
	     console.log('333',this.state.val)
	}

	render() {
		console.log('444',this.state.val)
	    return (
	      <div>
	        {`Counter is ${this.state.val}`}
	        <button onClick={this.batchUpdates}>点击我</button>
	      </div>
	    )
	}
}

在这里插入图片描述

可以发现,在函数batchUpdates里面有3次setState,但是我们每次点击的时候,val只是 + 1。

  • 在 setState 的时候react内部会创建一个updateQueue,通过 firstUpdatelastUpdatelastUpdate.next去维护一个更新的队列,在最终的 performWork中,相同的key会被覆盖,只会对最后一次的 setState 进行更新

上面code相当于下面这种:

class Demo extends Component {
    state = { val: 0 }
    
	batchUpdates = () => {
	    const currentCount = this.state.val;
	    
	    this.setState({val: currentCount + 1});
	    console.log('111',this.state.val)
	    
	    this.setState({val: currentCount + 1});
	    console.log('222',this.state.val)
	    
	    this.setState({val: currentCount + 1});
	    console.log('333',this.state.val)
	}
	render() {
	    console.log('444',this.state.val)
	    return (
	      <div>
	        {`Counter is ${this.state.val}`}
	        <button onClick={this.batchUpdates}>点击我</button>
	      </div>
	    )
	  }
}

currentCount就是一个快照结果重复给count设置同一个值,不要说重复3次,哪怕重复一万次,得到的结果也只是增加1而已

如果你想得到结果是3,应该怎么做呢?

这是就不需要对象式的参数,可以使用第二种函数式的参数:

class Demo extends Component {
    state = { val: 0 }

	batchUpdates = () => {
	    this.setState(prevState => ({
            val: prevState.val + 1
        }));
	     console.log('111',this.state.val)
	     
	     this.setState(prevState => ({
            val: prevState.val + 1
        }));
	     console.log('222',this.state.val)
	     
         this.setState(prevState => ({
            val: prevState.val + 1
        }));
	     console.log('333',this.state.val)
	}

	render() {
		console.log('444',this.state.val)
	    return (
	      <div>
	        {`Counter is ${this.state.val}`}
	        <button onClick={this.batchUpdates}>点击我</button>
	      </div>
	    )
	}
}

在这里插入图片描述
这样,每一次改变val的时候,都是prevState.val + 1,pervState是前一个状态,每次setState之后,前一个状态都会改变,那么这时候,结果就是想要的3了。

所以,如果需要立即 setState,那么传入一个函数式来执行setState是最好的选择。

5、setState的批量更新实例

注意区分函数式对象式的区别:

(1)对象式
class Example extends React.Component{
    state = {
        count: 0,
        num:100
    };
    componentDidMount(){
        this.setState({count: this.state.count + 1});
        console.log('a',this.state.count)

        this.setState({count: this.state.count + 7});
        console.log('b',this.state.count)

        this.setState({count: this.state.count + 4});
        console.log('c',this.state.count)

        this.setState(preState => {
            console.log('1111',preState)
            return{
                num: preState.num + 1
            }
        }, () => {
            console.log('d' , this.state.count)            
        })
    }
    render(){
        console.log('render',this.state.count)    
        return(
            <div>
                <div>count值:{this.state.count}</div>
                <div>num值:{this.state.num}</div>
            </div>
        )
    }
}

在这里插入图片描述

(2)函数式
class Example extends React.Component{
    state = {
        count: 0,
        num:100
    };
    componentDidMount(){
        this.setState(preState => ({ count: preState.count + 1 }))
        console.log('a',this.state.count)

        this.setState(preState => ({ count: preState.count + 7 }))
        console.log('b',this.state.count)

        this.setState(preState => ({ count: preState.count + 4 }))
        console.log('c',this.state.count)

        this.setState(preState => {
            console.log('1111',preState)
            return{
                num: preState.num + 1
            }
        }, () => {
            console.log('d' , this.state.count)            
        })
    }
    render(){
        console.log('render',this.state.count)    
        return(
            <div>
                <div>count值:{this.state.count}</div>
                <div>num值:{this.state.num}</div>
            </div>
        )
    }
}

在这里插入图片描述

(3)对象式和函数式混合
(1)实例一
class Example extends React.Component{
    state = {
        count: 0,
        num:100
    };
    componentDidMount(){
         this.setState({count: this.state.count + 1});
        console.log('a',this.state.count)

        this.setState({count: this.state.count + 7});
        this.setState(preState => ({ count: preState.count + 7 }))
        this.setState({count: this.state.count + 7});
        console.log('b',this.state.count)

        this.setState(preState => ({ count: preState.count + 4 }))
        
        this.setState({count: this.state.count + 6});
        console.log('c',this.state.count)

        this.setState(preState => {
            console.log('1111',preState)
            return{
                num: preState.num + 1
            }
        }, () => {
            console.log('d' , this.state.count)            
        })
    }
    render(){
        console.log('render',this.state.count)    
        return(
            <div>
                <div>count值:{this.state.count}</div>
                <div>num值:{this.state.num}</div>
            </div>
        )
    }
}

在这里插入图片描述

(2)实例二
class Example extends React.Component{
    state = {
        count: 0,
        num:100
    };
    componentDidMount(){
        this.setState(preState => ({ count: preState.count + 1 })) 
        console.log('a',this.state.count)

        this.setState({count: this.state.count + 7});
        console.log('b',this.state.count)

        this.setState(preState => ({ count: preState.count + 4 }))
        console.log('c',this.state.count)

        this.setState(preState => {
            console.log('1111',preState)
            return{
                num: preState.num + 1
            }
        }, () => {
            console.log('d' , this.state.count)            
        })
    }
    render(){
        console.log('render',this.state.count)    
        return(
            <div>
                <div>count值:{this.state.count}</div>
                <div>num值:{this.state.num}</div>
            </div>
        )
    }
}

在这里插入图片描述

(3)实例三
class Example extends React.Component{
    state = {
        count: 0,
        num:100
    };
    componentDidMount(){
      this.setState({count: this.state.count + 1});
      console.log('1:' + this.state.count)                //2==>0
      
      this.setState({count: this.state.count + 1});
      console.log('2:' + this.state.count)                //3==>0

      setTimeout(() => {
        this.setState({count: this.state.count + 1});
        console.log('3:' + this.state.count)             //9==>4
      }, 0)
      
      this.setState(preState => ({ count: preState.count + 1 }), () => {
        console.log('4:' + this.state.count)             //7==>3
      })
      console.log('5:' + this.state.count)                //4==>0
      
      this.setState(preState => ({ count: preState.count + 1 }))
      console.log('6:' + this.state.count)                 //5==>0
    }
    render(){
        console.log('render',this.state.count)    //1==>0   6==>3  8==>4
        return(
            <div>
                <div>count值:{this.state.count}</div>
                <div>num值:{this.state.num}</div>
            </div>
        )
    }

在这里插入图片描述

(4)结论:

通过观察,我们可以发现函数式对象式的setState有着细微的区别:

  1. 多个对象式,且属性相同时,会合并成一次setstate,只用看最后一个对象式的setState```
  2. 多个函数式不会合并成一个setState,必须计算每一个
  3. 对象式前面如果有函数式,则函数式setState不生效
  4. 函数式前面如果有对象式,则多次对象式合并为一次,只用看最后一次对象式
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值