React状态 -- setState的理解

618 阅读5分钟

setState参数

setState第一个参数传递函数

import React,{ Component } from 'react';
class A extends Component{
    
    state = {
        A: 1,
    }
    
    onChangeA = ()=>{
        this.setState((state,props)=>{
            A: state.A + 1
        })
        console.log('setState之后',this.state.A)    // 1  无法获取到最新的值
    }
    
    rebder(){
        const { A } = this.state
        return(
            <div>
                <div>A的值: { A ]</div> 
                <button onClick={ this.onChangeA }>改变A的值,第一个参数为函数</button>  
            </div>
        )
    }
}

setState第一个参数传递对象

import React,{ Component } from 'react';
class A extends Component{
    
    state = {
        A: 1,
    }
    
    onChangeA = ()=>{
        const A = this.state.A + 1;
        this.setState({ 
            A: A
        })
        console.log('setState之后',this.state.A)    // 1  无法获取到最新的值
    }
    
    rebder(){
        const { A } = this.state
        return(
            <div>
                <div>A的值: { A ]</div> 
                <button onClick={ this.onChangeA }>改变A的值,第一个参数为对象</button>  
            </div>
        )
    }
}

setState第二个参数Callback

setState第二个参数Callback: 何时执行: 在状态更新之后,界面更新之前调用

import React,{ Component } from 'react';
class A extends Component{
    
    state = {
        A: 1,
    }
    
    onChangeA = ()=>{
        const A = this.state.A + 1;
        this.setState({ 
            A: A
        },()=>{
            /*
                这个回调函数执行:
                    在状态更新之后,界面更新之前调用
            */
            console.log('setState之后',this.state.A)    // 2 在回调函数中可以获取到最新的值
        })
    }
    
    rebder(){
        const { A } = this.state
        return(
            <div>
                <div>A的值: { A ]</div> 
                <button onClick={ this.onChangeA }>改变A的值,第一个参数为对象</button>  
            </div>
        )
    }
}

总结

  • 传递对象的方式是传递函数的简写形式
    • 如果新状态不依赖于原状态 ===> 使用对象形式
    • 如果新状态依赖于原状态 ===> 使用函数形式
  • 如果需要在setState()后获取最新的状态,在第二个参数callback回调函数中读取
    • 这个callback在状态更新之后,界面更新之前(render)执行.

setState更新是同步还是异步?

react中setState({})是基于事务的机制

  • 执行setState()的位置
    • 在react控制的回调函数中: 生命周期钩子/react事件监听回调(onClick、onKeyDown不是原生的DOM事件)
    • 非react控制的异步回调函数中: 定时器回调 / 原生事件回调 / Promise回调 / ..
  • 异步 OR 同步?
    • react相关回调中: 异步
    • 其他异步回调中: 同步

在react生命周期钩子中或者react事件监听中(异步)

import React,{ Component } from 'react';
class A extends Component{
    state = {
        A: 0
    }
    clickOne = ()=>{
        console.log('update1 setState()之前'+this.state.A);   // update1 setState()之前1
        this.setState(state=>({ A:state.A+1 }));
        console.log('update1 setState()之后'+this.state.A);    // update1 setState()之后1
    };
    
    componentDidMount(){
        console.log('componentDidMount setState()之前'+this.state.A);     // componentDidMount setState()之前0
        this.setState(state=>({ A:state.A+1 }));
        console.log('componentDidMount setState()之后'+this.state.A);     // componentDidMount setState()之后0
    }
    
    render(){
       return(
            <div>
                { this.state.A }
                <div onClick={ this.clickOne }>事件中</div>
            </div>      
       )
    } 
}

在非react回调中

class A extends Component{
    state = {
        A: 0
    };
    
    /*
    * 定时器, 同步的
    * */
    setTimeClick = ()=>{
        setTimeout(()=>{
            console.log('setTimeout setState()之前'+this.state.A);
            this.setState(state=>({ A:state.A+1 }));
            console.log('setTimeout setState()之后'+this.state.A);
            /*
            * 打印结果
                setTimeout setState()之前1
                render()2
                setTimeout setState()之后2
            * */
        },300)
    }
    
    /*
    * 原生事件, 同步的
    * */
    nativeClick = ()=>{
        const h2 = this.refs.count
        h2.onclick = ()=>{
            console.log('onclick setState()之前'+this.state.A);
            this.setState(state=>({ A:state.A+1 }));
            console.log('onclick setState()之后'+this.state.A);
        }
        /*
        * 打印结果
        * componentDidMount setState()之前0
          render()1
          componentDidMount setState()之后1
        * */
    }
    
    /*
    * promise,   在await后面执行的也算
    * */
    promiseClick = ()=>{
        Promise.resolve().then(()=>{
            console.log('Promise setState()之前'+this.state.A);
            this.setState(state=>({ A:state.A+1 }));
            console.log('Promise setState()之后'+this.state.A);
        })
        /*
        * 打印结果
        * Promise setState()之前0
          render()1
          Promise setState()之后1
        * */
    };
    
    render(){
        const { A } = this.state
        console.log('render()'+A)
        return(
            <Card>
                <div > A的值: <h2 ref="count">{ A  } </h2></div>
                <Button onClick={ this.setTimeClick }>定时器</Button>
                <Button onClick={ this.nativeClick }>原生事件</Button>
                <Button onClick={ this.promiseClick }>Promise事件中</Button>
            </Card>
        )
    }
}

对于setState(fn)的调用

多次调用异步setState(),render必然只执行一次.

函数的模式setState(fn): 更新多次状态,但只调用一次render()更新界面 -- 状态更新没有合并,但是界面更新合并了

状态更新没有合并,界面更新合并了

class A extends Component{
    state = {
        A: 0
    }
    clickOne = ()=>{
        this.setState(state=>({ A:state.A+1 }));
        this.setState(state=>({ A:state.A+1 }));
        this.setState(state=>({ A:state.A+1 }));
    }
    
    render(){
       return(
            <div>
                { this.state.A }        // 最终页面输出3
                <div onClick={ this.clickOne }>事件中</div>
            </div>      
       )
    } 
    
}

对于setState({})的调用

多次调用异步setState(),render必然只执行一次.

对象的模式setState({}): 合并更新一次状态,只调用一次render()更新界面 --- 状态更新和界面更新都合并了

状态和界面都合并了

class A extends Component{
    state = {
        A: 0
    }
    clickOne = ()=>{
        this.setState({ A:this.state.A+1 });
        this.setState({ A:this.state.A+1 });
        this.setState({ A:this.state.A+1 });
    }
    
    render(){
       return(
            <div>
                { this.state.A }        // 最终页面输出3
                <div onClick={ this.clickOne }>事件中</div>
            </div>      
       )
    } 
    
}

对于setState({})和setState(fn)混用

只要最后一次调用的是对象模式--不管之前调用何种模式,都合并

只要最后一次调用的是函数模式--不管之前是对象模式还是函数模式,只要遇到对象模式,都合并之前的,然后累积函数模式的(状态更新)

class A extends Component{
    state = {
        A: 0
    }
    clickOne = ()=>{
        this.setState({ A:this.state.A+1 });
        this.setState({ A:this.state.A+1 });
        this.setState(state=>({ A:state.A+1 })); 
        this.setState({ A:this.state.A+1 });       // state.A截止这里为1
        this.setState(state=>({ A:state.A+1 }));   // 从这里开始累加 //2
        this.setState(state=>({ A:state.A+1 }));   // 3
    }
    clickTwo = ()=>{
        this.setState({ A:this.state.A+1 });
        this.setState(state=>({ A:state.A+1 }));
        this.setState(state=>({ A:state.A+1 }));
        this.setState({ A:this.state.A+1 });
        this.setState({ A:this.state.A+1 });    // 1
    }
    
    render(){
       return(
            <div>
                { this.state.A }        // 最终页面输出3
                <div onClick={ this.clickOne }>事件中</div>
                <div onClick={ this.clickTwo }>事件中</div>
            </div>      
       )
    } 
    
}

总结

关于异步的setState()

    1. 多次调用异步setState(),render必然只执行一次.
    1. 对于setState(fn)和setState({})的理解
    • 函数的模式setState(fn): 更新多次状态,但只调用一次render()更新界面 -- 状态更新没有合并,但是界面更新合并了
    • 对象的模式setState({}): 合并更新一次状态,只调用一次render()更新界面 --- 状态更新和界面更新都合并了
    • 混合模式(既有setState(fn)又有setState({}))
      • 只要最后一次调用的是对象模式--不管之前调用何种模式,都合并
      • 只要最后一次调用的是函数模式--不管是对象模式还是函数模式,只要遇到对象模式,都合并之前的,然后累积函数模式的(状态更新)

关于同步的setState()

  • 在非react控制的回调函数中,setState()是同步的,只要执行setState(),界面(render)立马更新,然后在执行其他的代码.