IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React学习笔记——this.setState的基础使用和不同传参方法详解 -> 正文阅读

[JavaScript知识库]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. 函数式前面如果有对象式,则多次对象式合并为一次,只用看最后一次对象式
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-03 11:05:05  更:2021-08-03 11:05:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 4:27:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码