第6节——state

本文详细介绍了React中state的概念、定义、使用方法,包括在构造函数和组件内部定义state,以及setState的不同用法。重点讨论了React18版本与17.x版本中setState的异步行为差异。
摘要由CSDN通过智能技术生成

一、概念及特点

state的主要作用是用于组件保存、控制以及修改自己的状态,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。

注意

如果直接通过this.state.xxx的方式修改,组件不会重新渲染,但是值会被修改。并且这也是不规范的行为

二、state的定义及使用

1、在constructor中定义state

import React from "react";

export default class LearnState extends React.Component {
  constructor() {
    super();
    /**
     * 定义当前组件的state
     * 这个对象里面可以存放任意数据
     */
    this.state = {
      name: '张三'
    };
  }

  render () {
    return (
      <div>
        {/* 
          使用的时候通过this找到state在找到对应要使用的属性
        */}
        <div>{this.state.name}</div>
      </div>
    )
  }
}

2、直接在组件内部定义state

注意

如果在constructor中定义了state,那么会覆盖组件内部直接定义的state

一般来说一个组件只会定义一个state对象,推荐直接在组件内部定义

import React from "react";

export default class LearnState extends React.Component {

  /**
   * 也可以直接在组件内部
   */
  state = {
    age: 10
  }

  render () {
    return (
      <div>
        <div>{this.state.age}</div>
      </div>
    )
  }
}

三、修改state

1、setState用法——第一个参数为函数

  /**
     * 第一个参数是一个函数接收两个参数
     * 函数的
     * 第一个参数为更新前的state
     * 第二个参数为组件的props
     */
    this.setState((preState, props) => {

      /**
       * 通过返回值修改state的状态
       * 返回那个值就修改state里面对应的值
       */
      return {
        num: preState.num + 1
      }
    })

2、setState用法——第一个参数为对象

 /**
     * 传入一个对象
     * 对象里面存放要修改state的数据
     * 这是最常用的修改方式
     */
    this.setState({
      num: 2
    })

四、使用setState对state进行赋值

1、对state中基本数据类型进行赋值

import React from "react";

export default class LearnSetState extends React.Component {
  state = {
    age: 18,
    name: "张三",
    isShow: false,
  };

  updateInfo () {
    /**
     * 基本数据类型 可以直接修改
     */
    this.setState({
      age: 19,
      name: '李四',
      isShow: true
    })
  }

  render() {
    return (
      <div>
        <div>哈哈 我是setState</div>
        <button onClick={() => this.updateInfo()}>更换信息</button>
        <div>
          {this.state.name}-{this.state.age}-{this.state.isShow}
        </div>
      </div>
    );
  }
}

3、获取最新的state的值

 this.setState({
      age: 19,
      name: '李四',
      isShow: true
      /**
       * setState可以传入第二个参数是一个回调函数
       * 该回调函数会在渲染完成后调用,可以在这里获取最新的state的值
       */
    }, () => {
      console.log(this.state)
    })

4、数组类型的赋值

    // 方法一:使用preState、concat创建新数组(不推荐,违反state是不可变数据的原则)
    this.setState((preState) => {
      return {
        books: this.state.books.concat(["红宝书"]),
      };
    });

    /**
     * 方法二:ES6数组扩展 (推荐)
     */
    this.setState(preState => ({
      books: [...preState.books, '红宝书']
    }))

5、对象类型进行赋值

    /**
     * 使用对象结构的方式进行赋值
     * 
     * 注意:如果对象巨大则不是则该方法不适应,后面会讲
     * 如何使用不可变数据更新state
     */
    this.setState({
      info: {
        ...this.state.info,
        name: '王五'
      }
    })

五、深入理解setState

1、react 18版本,点击按钮会会打印出什么

import React from "react";

export default class LearnSetState3 extends React.Component {
  state = {
    num: 1,
  };

  action() {
    setTimeout(() => {
      this.setState({
        num: 2,
      });
      console.log(this.state.num);
      this.setState({
        num: 3,
      });
      console.log(this.state.num);
    });
  }

  render() {
    return (
      <div>
        <button onClick={() => this.action()}>执行</button>
      </div>
    );
  }
}

最终输出 1 1

2、react 17.x版本,点击按钮会会打印出什么

以react@17.0.2 版本为主

重新使用create-react-app创建项目并把并把react版本改成17.0.2

index.js入口文件修改成

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

App.js

import React from "react";

export default class App extends React.Component {
  state = {
    num: 1,
  };

  add() {
    setTimeout(() => {
      this.setState({
        num: 2,
      });
      console.log(this.state.num);
      this.setState({
        num: 3,
      });
      console.log(this.state.num);
    });
  }

  render() {
    return (
      <div>
        <div>
          <button onClick={() => this.add()}>+1</button>
        </div>
      </div>
    );
  }
}

我们发现这两个log是同步执行

3、那么为什么会出现这个问题呢

我们先来梳理下 React 的渲染流程,包括 render 阶段、commit 阶段,render 阶段是从 vdom 转 fiber,包含 schedule 和 reconcile,commit 阶段是把 fiber 更新到 dom。渲染流程的入口是 performSyncWorkOnRoot 函数。

setState 会创建 update 对象挂到 fiber 对象上,然后调度 performSyncWorkOnRoot 重新渲染。

在 react17 中,setState 是批量执行的,因为执行前会设置 executionContext。但如果在 setTimeout、事件监听器等函数里,就不会设置 executionContext 了,这时候 setState 会同步执行。可以在外面包一层 batchUpdates 函数,手动设置下 excutionContext 来切换成异步批量执行。

在 react18 里面,如果用 createRoot 的 api,就不会有这种问题了。

setState 是同步还是异步这个问题在 react18 里就不会再有了,因为所有的 setState 都是异步批量执行了

有兴趣知道更多react 18 和 react 17两者区别的同学可以留言联系我们

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值