React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React state属性

React中state属性案例详解

作者:小小橘柚

在React中,state 是一个用于存储组件内部数据的特殊对象,每个React组件都可以包含自己的state,我们往往是通过修改state的值来驱动React重新渲染组件,这篇文章主要介绍了React中state属性,需要的朋友可以参考下

通过之前的文章自定义组件,我们发现创建的类式组件里面会存在state属性。在React中,state 是一个用于存储组件内部数据的特殊对象。每个React组件都可以包含自己的state,我们往往是通过修改state的值来驱动React重新渲染组件。

由于函数式组件this指向的是undifind所以,函数式组件不存在state

1)state案例

需求:点击按钮,切换标题内容

<script type="text/babel">
    // 创建类式组件
    class MyComponent extends React.Component {
        render() {
            console.log(this)
            return (
                <div>
                    <h1>内容</h1>
                    <button>按钮</button>
                </div>
            );
        }
    }
    /*渲染组件到页面*/
    ReactDOM.render(<MyComponent/>, document.getElementById("demo"))
</script>

我们是通过修改state的值来驱动内容的变化,那么我们想要使内容变化,按钮的点击事件就是修改state的值。那怎么初始化state呢,我们知道,state是实例的属性,所以我们想要初始化state的值需要在构造器里面实现。

构造器里面需要接受参数,应该是什么参数呢,通过查询官网,里面传递的参数为props,即:

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>
                <h1>内容</h1>
                <button>按钮</button>
            </div>
        );
    }
}

2)初始化state

有了构造器我们就可以在构造器里面初始化state的值了

class MyComponent extends React.Component {
    constructor(props, context, updater) {
        super(props, context, updater);
        // 初始化state
        this.state = {
            count: 0
        }
    }
    render() {
        return (
            <div>
                <h1>{this.state.count}</h1>
                <button>按钮</button>
            </div>
        );
    }
}

此时就可以查看到state的值了:

3)原生JS事件绑定

原生的 JavaScript 绑定事件有多种方式,以下是其中一些常见的方法:

使用 addEventListener 方法 这是一种推荐的方式,可以用于为一个 DOM 元素添加事件监听器。它可以用于绑定多个事件处理函数,而不会覆盖之前绑定的处理函数。

var element = document.getElementById("myElement");
element.addEventListener("click", myFunction);

内联事件处理函数: 你可以将事件处理函数直接写在 HTML 元素的属性中,如 onclickonmouseover 等。这不太推荐,因为将 HTML 和 JavaScript 混合在一起可能会使代码不够清晰。

<button onclick="myFunction()">点击我</button>

DOM 属性方式: 你可以直接为 DOM 元素的事件处理属性分配函数,这与内联事件处理函数类似,但是更清晰一些。

var element = document.getElementById("myElement");
element.onclick = myFunction;

4)React事件绑定

上面提到了原生的JS事件绑定方式,除了内敛事件处理函数其他都需要操作DOM,而我们的React是不需要操作DOM的,因此我们使用内敛事件处理函数对元素添加事件绑定。

class MyComponent extends React.Component {
    constructor(props, context, updater) {
        super(props, context, updater);
        this.state = {
            count: 0
        }
    }
    render() {
        return (
            <div>
                <h1>{this.state.count}</h1>
                <button οnclick="increment()">按钮</button>
            </div>
        );
    }
}
//注册点击事件
function increment() {
    console.log("按钮被点击了")
}

上面通过 onClick="increment()"给按钮注册了点击事件

通过上面的注册事件我们发现控制台报错了

image-20231022130311012

根据错误提示,告诉我们在尝试使用 onclick 事件处理属性时出现错误,提示是否想使用 onClick。所以我们把onclick变成onClick

render() {
    return (
        <div>
            <h1>{this.state.count}</h1>
            <button onClick="increment()">按钮</button>
        </div>
    );
}

但是发现还是报错了

image-20231022130609906

报错的意思是期望 onClick 事件监听器是一个函数,但实际上它是一个字符串类型的值。我们尝试使用JSX的表达式将函数赋值给onClick事件

<button onClick={increment()}>按钮</button>

此时我们发现,我们还没有点击按钮,一刷新网页,控制台立马打印了按钮被点击了这句话,也就是说在刷新网页的时候increment()立即执行了,点击按钮却得不到我们想要的结果。

我们再来分析一下onClick={increment()},这里面其实是将increment()执行结束之后的返回值赋值给onClick,而不是将函数赋值给onClick,所以increment()立即执行,然后将返回的undefined赋值给onClick,所以点击按钮当然没有作用,我们能做的就是把increment函数本身赋值给onClick

<button onClick={increment}>按钮</button>

此时点击按钮成功打印结果,控制台也不存在报错。

5)类中方法this指向

解决了点击事件,我们下面的目标就修改state里面的count,我们能直接在increment函数里面修改吗?组件的实例并不是我们创建的,而且函数在组件(类)的外面,incrementthis指向的是undefined,也就是说increment函数根本访问不到组件的this,从而也就访问不到组件的state

我们可以把increment函数写在类组件里面,作为类的一般方法。

class MyComponent extends React.Component {
    constructor(props, context, updater) {
        super(props, context, updater);
        this.state = {
            count: 0
        }
    }
    render() {
        return (
            <div>
                <h1>{this.state.count}</h1>
                <button onClick={increment}>按钮</button>
            </div>
        );
    }
    // 注意作为一般方法,不需要使用function修饰
    increment() {
         console.log("按钮被点击了"+this.state.count)
    }
}

此时我们发现控制台又报错了,提示我们increment未定义,可是我们已经定义了的

我们实现的increment函数作为一般方法,可以通过类的实例调用,而onClick={increment}根本访问不到,所以修改成下面:

<button onClick={this.increment}>按钮</button>

此时报错解决了,但是我们点击按钮的时候,报错又接踵而至:

根据提示我们发现state读取不到,可是我们的state就是挂载在this上面的属性,怎么会读取不到呢,我们通过在increment函数打印this查看:

increment() {
    console.log(this)
    console.log("按钮被点击了"+this.state.count)
}

我们发现打印的结果是undefined,这令我们很奇怪,方法定义在类里面,怎么会访问不到this呢,而同样为类的方法render却可以访问到this呢?

我们再回顾一下创建类式组件以及渲染类式组件的过程,我们定义组件重写了render方法,返回VDOM,而React想要渲染DOM需要创建组件的实例,然后**通过实例调用render**方法。我们知道类里面的方法是挂载到原型对象上的,只有通过类的实例调用方法才能访问到this,而我们创建的组件实例并不是有我们创建的,我们只是把increment函数赋值给onClick,所以increment可能不是通过实例去调用的。

6)类中方法this指向案例

我们创建一个类,类里面有speak方法,用于打印this

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    speak(){
        console.log(this)
    }
}

创建实例,调用方法

 const p=new Person('jvyou',22)
 p.speak()

此时成功打印出来p的this,说明通过类的实例调用类里面的方法,方法里面的this就是指向实例的

我们将p.speak赋值给其他变量尝试直接调用试试:

const s = p.speak
s()

输出的结果却是undefined,这个属于直接调用,原本的this应该是window,但是由于严格模式下就是undefind

通过这个案例我们再回顾以下我们的代码:

<button onClick={this.increment}>按钮</button>

我们将this.increment赋值给onClick,在点击按钮的时候React会直接调用onClick,根本不是通过组件的实例调用的,所以根本访问不到组件的this,所以也访问不到组件的state

7)解决this指向undefined

我们知道可以通过bind方法给函数绑定this,我们可以在构造函数里面访问到this,我们可以通过在构造函数里面给increment方法绑定实例的this,再将绑定成功的新的函数挂载到对象的属性上。

constructor(props, context, updater) {
    super(props, context, updater);
    this.state = {
        count: 0
    }
    this.increment = this.increment.bind(this)
}

this.increment.bind(this)绑定this之后返回一个新的函数,此时函数内部的this指向的就是创建的对象

再将返回的函数赋值给this.increment,作为属性挂载到对象上。

此时再点击按钮,成功打印出组件的this,且发现increment也变成组件属性了,且原型链上面也存在increment方法,属性上的increment是通过原型链上的increment绑定this得来的,因此onClick={this.increment}是将属性上的increment赋值给onClick

8)修改state

通过上面的方式我们就可以在方法里面修改this.state

increment() {
    this.state.count++
    console.log("按钮被点击了" + this.state.count)
}

但是我们发现点击了按钮不报错,count的值也一直为0不改变

这是因为state不能直接更改,需要使用React的内置API

我们可以通过打印组件的this,在原型链上找到API:setState,所以可以直接通过this访问即可

increment() {
    const c = this.state.count + 1
    this.setState({count: c})
    console.log("按钮被点击了" + this.state.count)
}

此时成功解决。但是在实现的时候使用了const c = this.state.count + 1,嫩不能改成const c = this.state.count++呢,是不能的,因为这二样会直接修改state的值。

state存在多个属性怎么修改呢

constructor(props, context, updater) {
    super(props, context, updater);
    this.state = {
        count: 0,
        name: '橘柚'
    }
    this.increment = this.increment.bind(this)
}

上面代码state里面存在两个属性

increment() {
    const c = this.state.count + 1
    this.setState({count: c})
}

这里面只修改了count,但不会影响到name

9)state的简写

简化state

我们在上面初始化state的时候是在构造器里面实现的,但是我们知道,类里面可以直接定义类的属性,而且可以直接给属性赋值,所以我们就不需要在构造器里面初始化state,直接修改成:

class MyComponent extends React.Component {
    //初始化state,直接作为类组件的属性定义
    state = {
        count: 0,
        name: '橘柚'
    }
    // 构造器
    constructor(props, context, updater) {
        super(props, context, updater);
        this.increment = this.increment.bind(this)
    }
    ...省略render和increment
}

简化事件

我们知道increment函数在MyComponent原型链上不通过实例调用会导致this指向丢失,所以我们通过bind挂载this解决,即this.increment = this.increment.bind(this),但是我们知道箭头函数的 this 指向是与普通函数不同的。箭头函数的 this 指向是继承自包含它的最近的父级(词法作用域)函数的 this 值,而不是动态绑定的。所以只需要将increment变成箭头函数,在创建实例的时候incrementthis自己就会指向实例,无需再次绑定。

increment = () => {
    const c = this.state.count + 1
    this.setState({count: c})
    console.log("按钮被点击了" + this.state.count)
}

此时我们打印this,也能在组件的属性上面找到increment

一开始我们添加构造器是为了初始化state以及为事件绑定this,现在全部解决了,构造器也就不用存在了,所以最后的代码如下:

class MyComponent extends React.Component {
    state = {
        count: 0,
        name: '橘柚'
    }
    increment = () => {
        const c = this.state.count + 1
        this.setState({count: c})
        console.log("按钮被点击了" + this.state.count)
    }
    render() {
        return (
            <div>
                <h1>{this.state.count}{this.state.name}</h1>
                <button onClick={this.increment}>按钮</button>
            </div>
        );
    }
}

10)setState 扩展

对象式setState

我们可以使用 setState 来更新组件的状态,但是 setState 有两种常见的写法,上面我们一直用的是传递一个对象,即:

this.setState({ count: this.state.count + 1 });

这种写法直接传递一个新的状态对象给 setState。React会将新状态与当前状态合并,并在后续的更新周期中应用这个新状态。

但其实setState还有第二个参数:

setState( stateChange , [callback] )
stateChange :状态改变对象

​ callback:可选回调函数,它在状态更新完毕,render调用后,才被调用

这个回调有什么用呢,我们先看一个案例:

export default class Count extends Component {
    state = {
        count: 0
    }
    increment = () => {
        const {count} = this.state
        this.setState({count: count + 1})
        console.log("@count:", this.state.count)
    }
    render() {
        return (
            <div>
                <h1>当前Count:{this.state.count}</h1>
                <button onClick={this.increment}>+1</button>
            </div>
        )
    }
}

当我们点击了“+1”的按钮之后,页面展示的效果确实是1,但是打印的结果却是0,我们在打印count的时候已经执行 this.setState({count: count + 1})进行状态更新了,为什么还是0呢?那是因为setState 可能是异步的,所以在读取 this.state 之前不应该依赖于它的值。

如果需要在状态更新后执行一些操作,可以将这些操作放在 setState 的回调函数中:

 increment = () => {
     const {count} = this.state
     this.setState({count: count + 1}, () => {
         console.log("@count:", this.state.count)
     })
 }

函数式setState

传递一个更新函数:

this.setState((prevState, props ) => {
  return { count: prevState.count + 1 };
} , [callback]);

这种写法将状态的更新逻辑封装在一个函数中,并接受参数 prevStatepropsprevState代表当前状态的先前值。

这两种写法的选择取决于你的需求和使用情境。通常情况下,使用更新函数的方式更加安全,因为它可以确保基于当前状态来计算新状态,避免了潜在的竞态条件和不一致性。另外,使用更新函数的方式也适用于异步更新状态的情况。

到此这篇关于React中state属性的文章就介绍到这了,更多相关React state属性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文