React State(类组件响应式数据)

135 阅读8分钟

React State

React 的核心思想是组件化的思想,项目由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件渲染时的数据依据,在Vue2.0中就是数据源,Vue3.0是被Proxy代理的响应式数据。

组件中用到的一个变量是不是应该作为组件的响应式数据State,可以通过下面的4条依据进行判断,并不是组件中用到的所有变量都是组件的状态

  • 这个变量如果是通过Props从父组件中获取的,那么它不应该是一个状态,从父组件获取的值不能在子组件中改变。
  • 这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。
  • 这个变量是否可以通过其他状态(State)或者属性(Props)计算得到?如果是,那么它不是一个状态。
  • 这个变量是否在组件的render方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性。

定义State

响应式数据定义在类的state属性中

注意:React在ES6的实现中,规定state在constructor中实现

// home.js
import React, { Component } from "react";
export default class Home extends Component {
    // constructor是用Home类来创建对象的方法,this就是Home类
    constructor(props) {
        super(props);
        // 组件的state状态
        this.state = {
            // 这里写state,也就是响应式数据,类似于vue2data方法返回的对象
            data: '响应式数据'
        };
        // 组件的一个普通属性
        this.msg = 'Home组件'
        console.log(this, 666);
    };
    render() {
        // render方法是Home类创建出的对象的方法,this就是Home类
        console.log(this, 777);
        return (
            <div>
                {/* 在react的模版中使用类的属性必须用this来获取,而Vue可以省略this */}
                <p>{this.msg}</p>
                <p>{this.state.data}</p>
            </div>

        )
    }
}

image.png

在ES7新语法中,React的state不是必须在constructor中实现

import React, { Component } from "react";
export default class Home extends Component {
    state = {
        // 这里写state,也就是响应式数据,类似于vue2data方法返回的对象
        data: '响应式数据data'
    };
    // 组件的一个普通属性
    msg = 'Home组件'
    render() {
        return (
            <div>
                {/* 在react的模版中使用类的属性必须用this来获取,而Vue可以省略this */}
                <p>{this.msg}</p>
                <p>{this.state.data}</p>
            </div>

        )
    }
}

image.png

react响应体系原理

  1. reac不能像vue一样直接修改state状态即修改响应式数据就会触发更新
  2. react是可以修改state状态,需要再调用setState来触发更新操作
import React from "react";
class App extends React.Component {
    // constructor是用App类来创建对象的方法,this就是App类
    constructor() {
        super()
        this.msg='App组件';
    };
    render() {
        // render方法是App类创建出的对象的方法,this就是App类
        let info='xwlb';
        let change=()=>{
            console.log(info,111);
            console.log(this.msg,222);
            this.msg='App类';
            info='yyds';
        };
        let look=()=>{
            console.log(info,666);
            console.log(this.msg,777);
        }
        return (
            <div>
                <p>{this.msg}</p>
                <p>{info}</p>
                <button onClick={change}>change</button>
                <button onClick={look}>look</button>
            </div>

        )
    }
}
export default App;

image.png

状态没有改变,变量保存的数据改变了,但是页面并没有刷新。因为render函数只在页面初始加载时执行了一次,只取了第一次的值。所以需要将数据设置到State中,才会变成响应式的数据。

在React中,调用this.setState就会重新执行一次当前组件的render函数,render函数的代码就会重新运行一次,返回的JSX对象会重新取值

正确定义State的方式如下

  • 1)在类中定义state属性
  • 2)通过bind函数来劫持this对象即当前组件,或者使用箭头函数(事件函数是用来改变状态)
  • 3)在事件函数内部使用setState函数更改状态
  • 4)在组件中的render函数中使用该状态
  • 5)在组件上需要设置监听事件,去触发事件函数的执行
import React from "react";
class App extends React.Component {
      //constructor表示构造器,在constructor需要声明状态state,
     //在声明state之前需要使用super(props);
    constructor(props) {
        super(props)//使用父类的属性
        this.msg='App组件';
        //声明状态
        this.state={
            title:'React 响应式'
        }
        this.changestate=()=>{
            this.state.title='设置状态';
        };
        this.lookstate=()=>{
            console.log(this.state.title,1989);
        }
    };
    render() {
        let info='xwlb';
        let change=()=>{
            console.log(info,111);
            console.log(this.msg,222);
            this.msg='App类'
            info='yyds';
        };
        let look=()=>{
            console.log(info,666);
            console.log(this.msg,777);
        }
        return (
            <div>
                <p>{this.msg}</p>
                <p>{info}</p>
                <button onClick={change}>change</button>
                <button onClick={look}>look</button>
                <h1>{this.state.title}</h1>
                <button onClick={this.changestate}>changestate</button>
                <button onClick={this.lookstate}>lookstate</button>
            </div>

        )
    }
}
export default App;

image.png

设置状态

  • 语法:setState(object nextState\[ function callback]),该方法可以传入一个对象也可以传入一个函数。传入的对象中的属性如果是之前state对象有的属性就更新,如果是没有的属性就创建。传入函数时需要返回一个对象,传入的函数的参数接受之前的state对象。
  • setState方法是React事件处理函数中和回调函数中触发页面更新的主要方法。
  • setState()不一定是同步的也不一定是异步的,为了性能提升,React会批量执行state和DOM渲染
  • setState()总是会触发一次组件重绘,但可在shouldComponentUpdate()这个生命周期函数中实现一些条件渲染逻辑来解决。
import React from "react";
class App extends React.Component {
    // constructor是用App类来创建对象的方法,this就是App类
    constructor(props) {
        super(props)
        this.msg='App组件';
        this.state={
            title:'React 响应式'
        };
        this.changestate=()=>{
            console.log(this.state.title,2009);
            // 传入对象时,传入的对象就是修改的数据
            this.setState({title:'设置状态',connt:'0530'});
            // this.state.title='设置状态';
            // this.setState(this.state);
            // 传入函数时,函数所返回的就是要修改的数据
            // this.setState((prevState) => {
                // prevState是前一个状态
                // return {
                    // title: prevState.title + "--"
                // }
            // });
        };
        this.lookstate=()=>{
            console.log(this.state,1989);
        }
    };
    render() {
        // render方法是App类创建出的对象的方法,this就是App类
        let info='xwlb';
        let change=()=>{
            console.log(info,111);
            console.log(this.msg,222);
            this.msg='App类'
            info='yyds';
        };
        let look=()=>{
            console.log(info,666);
            console.log(this.msg,777);
        }
        return (
            <div>
                <p>{this.msg}</p>
                <p>{info}</p>
                <button onClick={change}>change</button>
                <button onClick={look}>look</button>
                <h1>{this.state.title}</h1>
                <button onClick={this.changestate}>changestate</button>
                <button onClick={this.lookstate}>lookstate</button>
            </div>

        )
    }
}
export default App;

image.png

数据改变后页面也刷新了,响应式数据必须写在state对象中,只有执行setState函数才会更新页面。底层会修改this的state对象的属性值,然后利用diff算法去刷新页面,执行setState函数就会重新运行render函数生成新的VNode来更新页面。

// 第一种修改方式
this.setState({title:'设置状态'});
// 第二种修改方式
this.state.title='设置状态';
this.setState(this.state);

第一种和第二种是等价的,都会刷新页面,但是更多使用第二种,可以批量改变数据然后统一刷新页面。

import React from "react";
class App extends React.Component {
    // constructor是用App类来创建对象的方法,this就是App类
    constructor(props) {
        super(props)
        this.msg='App组件';
        this.state={
            title:'React 响应式'
            msg:"xwlb"
        };
        this.changestate=()=>{
            console.log(this.state.title,2009)
            this.state.title='设置状态';
            this.state.msg='xwlb yyds';
            // 批量改变数据然后统一刷新页面
            this.setState(this.state);
        };
        this.lookstate=()=>{
            console.log(this.state,1989);
        }
    };
    render() {
        // render方法是App类创建出的对象的方法,this就是App类
        let info='xwlb';
        let change=()=>{
            console.log(info,111);
            console.log(this.msg,222);
            this.msg='App类'
            info='yyds';
        };
        let look=()=>{
            console.log(info,666);
            console.log(this.msg,777);
        }
        return (
            <div>
                <p>{this.msg}</p>
                <p>{info}</p>
                <button onClick={change}>change</button>
                <button onClick={look}>look</button>
                <h1>{this.state.title}</h1>
                <button onClick={this.changestate}>changestate</button>
                <button onClick={this.lookstate}>lookstate</button>
            </div>

        )
    }
}
export default App;

this.msgthis.state.title在执行this.setState(this.state)后,页面的数据改变了。因为this.setState(this.state)的执行会再次让render函数执行,模板的{}中变量会再次取值,取得就是变量改变后的值。而render函数中的info变量在每次render函数执行时都会重新生成局部变量。

2.gif

setState工作流程

image.png

setState工作分成两步:

  1. 将之前的state对象和传入setState方法的对象进行浅合并(Object.assign方法)来修改数据
  2. 然后触发更新页面的操作
import React from "react";
class App extends React.Component {
    state = {
            title:'React 响应式'
            msg:"xwlb",
            c: {
                c1:99,
                c2:66
            }
            
        }
    render() {
        let change=()=>{
            this.setState({
                title:"IAG",
                msg:"XWLB YYDS",
                c:{
                    c1:77
                }
            })
        };
        return (
            <div>
                <p>{this.state.c.c1}</p>
                <h1>{this.state.c.c2}</h1>
                <button onClick={this.change}>change</button>
            </div>

        )
    }
}
export default App;

此时原来state对象的c属性中的c2属性就是undefined了,因为是浅合并,原来的c属性整个会替换为{c1:77}这个对象,就没有c2属性了,对象中取不到c2属性就是undefined

this.setState()是异步操作

因为this.setState()方法不是同步的,它是异步的。

而业务中有时候需要获取修改后的数据做下一个操作,就需要化同步为异步。此时就需要用到setState方法的第二个参数,第一个参数是要修改的数据的对象,第二个参数是回调函数。

import React from "react";
class App extends React.Component {
    // constructor是用App类来创建对象的方法,this就是App类
    constructor(props) {
        super(props);
        this.state={
            title:'React 响应式'
        };
        this.changestate=()=>{
            console.log(this.state.title,2009);
            this.setState({title:'设置状态'});
            // 同步输出的title属性还是原来的值,但是页面的值已经修改了
            console.log(this.state.title,729);
        };
    };
    render() {
        // render方法是App类创建出的对象的方法,this就是App类
        return (
            <div>
                <h1>{this.state.title}</h1>
                <button onClick={this.changestate}>changestate</button>
            </div>

        )
    }
}
export default App;

2.gif

当把对象传入this.setState()中修改,并没有立刻将数据修改然后刷新页面,它是异步完成的。它不会阻塞同步代码执行,下面同步代码就会取出修改之前的值并打印。

解决方法就是传入一个回调函数,在回调函数中来获取修改之后的值。回调函数是在DOM刷新页面之后才会执行,此时的值就是修改之后的值。

import React from "react";
class App extends React.Component {
    // constructor是用App类来创建对象的方法,this就是App类
    constructor(props) {
        super(props);
        this.state={
            title:'React 响应式'
        };
        this.changestate=()=>{
            console.log(this.state.title,2009);
            this.setState({title:'设置状态'},()=>{
            // 在这个回调函数中就可以获取到页面更新后的值,等同于vue的nextTick操作
            console.log(this.state.title,729);
        });
        };
    };
    render() {
        // render方法是App类创建出的对象的方法,this就是App类
        return (
            <div>
                <h1>{this.state.title}</h1>
                <button onClick={this.changestate}>changestate</button>
            </div>

        )
    }
}
export default App;

image.png

在vue中希望想要操作基于最新数据生成的DOM时,就将这个操作放在 nextTick 的回调中。

setState的一些特性

  1. setState方法多次调用进行数据修改,它只会合并为一次进行统一更新
  2. setState方法会触发更新,不管是否有数据进行修改。这造成了一个问题,重复将数据都修改为相同的值也会让组件更新。
  3. 一定不要在render函数中直接调用setState,会进入死循环(调用setState就会运行render函数,render函数中又调用setState,如此便进入死循环)

image.png