React组件三大核心属性 - State

1,073 阅读5分钟

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

一、state的理解

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

向组件中添加局部状态:

组件的局部状态(16.8版本之前)只存在于class声明的组件,函数声明的组件无state 所以我们称函数声明的组建为无状态组件,16.8版本后react 添加了Hook的概念让函数组件拥有了局部状态(无状态组件说法就被舍弃了)

除了使用外部数据(通过 this.props 访问)以外,组件还可以维护其内部的状态数据(通过 this.state 访问)。当组件的状态数据改变时,组件会再次调用 render() 方法重新渲染对应的标记。

// 1、创建类组件
class ComponentA extends React.Component {
    constructor(props) {
        super(props);
        // 初始化状态
        this.state = {
            name: '小明', 
            age: 18
        }
    }
    render() {
        //  读取状态
        const { name, age } = this.state;
        return (
            <div>
                <h2>我是组件A</h2>
                <p>render函数就是我的渲染函数</p>
                <p>name: {name}</p>
                <p>age: {age}</p>
            </div>
        )
    }
}
// 2、渲染组件到页面
ReactDOM.render(<ComponentA />, document.getElementById('test'));

注意:

  1. 每个class都由constructor方法,当class代码中没有显示声明constructor构造函数时,class会自动隐式添加该方法
  2. 使用class继承的类,在其constructor构造函数中必须首先调用构造函数自带的super方法在继承的父类中完成this对象的塑造并继承父类的属性方法.如果不调用该方法子类将没有this对象
  3. 如果你的react class组件内部不需要创建state 绑定方法或者任何在constructor构造函数中要执行的代码推荐隐式创建constructor

二、react中的事件绑定

onClick 而不是 onclick,react中把方法是小写的都重写了一遍,比如onblur ==> onBlur

onClick={}, 不能写成onClick={test()}因为页面进来执行render()之后会立即执行test方法,把undefined赋给了onClick,所以当你再次点击标题的时候是没有反应的,因为此时为undefined,react中如果遇到是undefined是不做任何动作的

class ComponentA  extends React.Component {
    constructor(props) {
        super(props);
        // 初始化状态
        this.state = {
            isHot: true
        }
    }
    render() {
        //  读取状态
        const { name, age } = this.state;
        return (
            <div>
                <h2>我是组件A</h2>
                <p>render函数就是我的渲染函数</p>
                <p>name: {name}</p>
                <p>age: {age}</p>
                <p onClick={test}>点我</p>
            </div>
        )
    }
}

function test() {
    console.log('我被点击了');
}

ReactDOM.render(<ComponentA  />, document.getElementById('test'));

三、类中方法中的this

class Weather extends React.Component {
    constructor(props) {
        super(props);
        // 初始化状态
        this.state = {
            isHot: true
        }
    }
    render() {
        //  读取状态
        const { isHot } = this.state;
        // changeWeather 作为 onClick 的回调,是直接原型链中找到存放起来,然后再赋值给onClick,,不是通过实例调用的,而是直接调用,所以 this 不指向实例
        // return <h1 onClick={changeWeather}>今天天气很{isHot ? '炎热' : '凉快'}</h1>  error: changeWeather is not defined
        return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉快'}</h1>
    }
    changeWeather() {
        console.log(this); // undefined  因为changeWeather不是实例对象调用的
    }
}

ReactDOM.render(<Weather />, document.getElementById('test'));

问题一:定义的changeWeather方法是位于什么位置的?

答: Weather的原型对象上,供实例使用,通过Weather实例调用changeWeather时,changeWeather中的this就指向Weather实例

问题二:其中的this是什么?

答: undefined,因为类中定义的方法在局部自动开启了严格模式,所以this为undefined

四、this指向问题

我们上面已经说了调用了方法之后this是undefined,那这个问题怎么解决呢?

这里使用bind() 这个方法,只需在constructor中改变方法的this指向问题

constructor(props) {
    super(props);
    // 初始化状态
    this.state = {
        isHot: true
    }
    // 改变this指向
    this.changeWeather = this.changeWeather.bind(this);
}

问题:bind做了什么?

答: bind: 做了两件事情 ---- 生成新的函数并且改变this为Weather的实例对象;this.changeWeather是原型上的方法,通过bind改变this之后生成新的方法放在了实例自身上,导致了实例中也有changeWeather这个方法,这样就能进行调用了。

注意: 没有执行bind之前this为undefined,因为类中定义的方法在局部自动开启了严格模式,所以this为undefined

image.png

执行bind之后this指向Weather的实例对象

image.png

五、组件中局部状态更新问题:

我们打印React class组件的state发现,它并没有像Vue一样给每个state属性进行数据劫持,只是一个普通对象。这样就导致如果直接修改class 组件state是不会引起页面刷新的。

在React中修改局部状态state,必须使用this.setState()

// Wrong 此代码不会重新渲染组件
this.state.age = 20; 

// Correct 
this.setState({age: 20});

this.setState是浅合并,也就是说this.setState({age})完整保留了this.state.name,但完全替换了this.state.age。你可以调用 setState() 独立地更新state中任意属性

六、setState的使用

这里其实说的就是第五点中的状态更新问题

  1. 状态里的数据不允许直接更改,要借助一个内置的API更改
  2. 状态必须通过setState进行更改,且更新是一种合并操作,不是替换
// Wrong 此代码不会重新渲染组件 
this.state.age = 20; 

// Correct 
this.setState({
    age: 20
});

七、State的简写

其实这个方式也可以解决上面提到的this为undefined的问题

但是有一点不同的是没有使用构造器,方法改为箭头函数,然后state我们直接写成一个对象,就像那个普通对象添加属性,有就进行替换,没有就新增。

class Weather extends React.Component {

    // 初始化状态
    state = { isHot: true, wind: '大风' }

    render() {
        const { isHot, wind } = this.state;
        return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}, {wind}</h1>
    }

    // 自定义方法  --- 要用赋值语句的形式+箭头函数
    changeWeather = () => {
        // 箭头函数本身没有this,this是外层的this,即Weather
        const isHot = this.state.isHot;
        this.setState({
            isHot: !isHot
        })
        // console.log(this);   // Weather
    }
}

ReactDOM.render(<Weather />, document.getElementById('test'));

八、小总结

  1. 组件中render方法中的this为组件实例对象

  2. 组件自定义的方法中this为undefined,怎么解决?

    • 强制绑定this: 通过函数对象的bind() 【构造器写法】

    • 赋值语句+箭头函数 【无构造器写法】

  3. 状态数据,不能直接修改或更新,必须通过setState进行更改