# react 使用 state

66 阅读11分钟

react 使用 state

前面几篇博文我们介绍了 函数式组件 和 类式组件,然后今天来看一下 state状态,很简单。

怎么区分函数式组件和类式组件

之前说函数式组件和类式组件的时候我们说过,函数式组件主要实现简单组件的定义,类式组件主要实现复杂组件的定义,那什么是简单组件,什么是复杂组件,其实很简单,就是我们今天要了解的 state

简单点说:

  • 如果你的组件是有状态的 state ,就是复杂式组件。
  • 如果你的组件没有状态 state,那他就是简单组件。

state 状态

好了,我们知道通过 state 的有无来判断你的组件是不是复杂组件,那新的问题又来了,什么状态 state

【理解一下】

打个比方:

比如说有一天,老师问小明,考试为啥没考好啊? 小明回答说,因为我状态不好。

OK,仔细理解上面这个例子,我们可以得出一个结论:

是有 状态 的,人的 状态 会 影响 行为

同样,组件 也是有 状态 的,状态 会 驱动 页面

简单理解:组件的状态里面存放着数据,数据的改变就会驱动着页面的展示。

案例引入 state

接下来写一个案例,然后我们通过这个案例把 state 相关的基础东西都说一下。

在这里插入图片描述 一个页面,内容很简单,就是一个单纯的标签,点击标签更换标签显示的内容。

初始化状态

状态的初始化很简单,我们可以看 官网【穿梭门】 给的一个很基本的案例。

在这里插入图片描述

我们看到他是使用的 构造器函数,使用 this.state={ xxx : xxx } 完成初始化状态的,然后构造器函数传了一个 props 值,并且还继承了父类,这个 props 是啥我们先不要管,后边博客再说好了,然后我们先照着抄过来,实现我们这个案例的基本功能。

假设我们有一个状态值是 isWork, 如果是 true 就显示上班,如果是 false 就显示不上班。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>11.state</title>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="app"></div>
    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom 用于支持 react 操作 dom -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入 label,用于将 jsx 转化为 js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <!-- 此处必须写 text/babel -->
    <script type="text/babel">
        // 1. 创建类式组件
        // 注意:创建类式组件必须继承 React.Component
        class Demo extends React.Component {
            constructor(props) {
                super(props)
                // 初始化状态 state
                this.state = { isWork: true }
            }
            render() {  // render 必须写,并且必须有返回值
                let { isWork } = this.state
                return <h1>老子今天{isWork ? '要' : '不'}上班!</h1>
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Demo />, document.getElementById('app'))

    </script>

</body>

</html>

好的,保存刷新一下,我们看一下效果。

在这里插入图片描述

OK呀,因为是 true ,所以显示出来了。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

标签点击事件绑定

好的,上边一部分可以实现根据状态值 isWork 的不同,渲染去出上班不上班,所以说,现在我们需要一个步骤,就是触发,让上班不上班可以来回切换。

这个第一反应应该就是采用点击事件了对吧?然后我们先会议一下原生的点击事件有哪些好吧!

原生点击事件实现

原生实现点击事件一共有三种方式:

  • addEventListener
  • onclick
  • 调用方法

下边关键代码写一下。

<body>
  <button id="btn1">按钮一</button>
  <button id="btn2">按钮二</button>
  <button onclick="demo()">按钮三</button>

  <script type="text/javascript">

    const btn1 = document.getElementById('btn1')
    btn1.addEventListener('click', () => {
      alert('按钮一被点击了!')
    })

    const btn2 = document.getElementById('btn2')
    btn2.onclick = () => {
      alert('按钮二被点击了!')
    }


    function demo() {
      alert('按钮三被点击了!')
    }

  </script>
</body>

我们看一下效果。

在这里插入图片描述

可以看到这三个方式都是可以实现点击事件的。

React 实现点击效果呢,按钮一按钮二 的方式都是可以的,没有任何问题。我们在之前的代码上面稍微改一下,然后写一下这两种方式触发点击事件。

    <script type="text/babel">
        // 1. 创建类式组件
        class Demo extends React.Component {
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = { isWork: true, day: '星期五' }
            }
            render() {
                console.log(this)
                let { isWork, day } = this.state
                return <h1 id="title">老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Demo />, document.getElementById('app'))

		// 方式一
        const title = document.getElementById('title')
        title.addEventListener("click", () => {
            console.log("标题被点击了")
        })

		// 方式二
        const title  = document.getElementById('title')
        title.onclick = () => {
            console.log("标题被点击了")
        }

    </script>

代码在上面了,这两种点击事件的方法都是可以的,但是 React 一般情况下不会这样使用,一般采用 按钮三 的方式编写点击事件。

React 绑定事件

接下来我们使用 调用函数 的方式的方式实现点击事件。

我们修改一下代码:

    <script type="text/babel">
        // 1. 创建类式组件
        class Demo extends React.Component {
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = { isWork: true, day: '星期五' }
            }
            render() {
                console.log(this)
                let { isWork, day } = this.state
                return <h1 id="title" onClick="demo()">老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Demo />, document.getElementById('app'))

        function demo() {
            console.log("标题被点击了")
        }
    </script>

保存刷新,我们查看一下结果。

在这里插入图片描述

我们看到,我们啥都没有干,控制台直接报错了,报错的意思是 onClick 需要一个方法,但是现在传入的是字符串,学过 vue 的小伙伴可能灵光一下,觉得之前说过,如果调用要使用 { } 包裹起来。我们修改一下代码试一下。

    <script type="text/babel">
        // 1. 创建类式组件
        class Demo extends React.Component {
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = { isWork: true, day: '星期五' }
            }
            render() {
                let { isWork, day } = this.state
                return <h1 id="title" onClick={demo()}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Demo />, document.getElementById('app'))

        function demo() {
            console.log("标题被点击了")
        }
    </script>

保存刷新,查看结果。

在这里插入图片描述 一刷新页面,啥也没动发现控制台就已经打印出来数据了,而且当再次点击的时候,没反应了。这是为啥呢?因为 onClick 会把 { } 中包裹内容的返回值赋值给 onClick,恰巧 { } 中包裹的 demo() 是一个方法,里面的返回值是一个 undefined,在渲染的时候,React 会直接把 demo() 函数给执行了,啥也没返回,所以会直接打印,但是 onClick 啥也没绑定上,所以点击没反应。如何解决这个问题呢?很简单,只要不加括号就可以了。

<script type="text/babel">
        // 1. 创建类式组件
        class Demo extends React.Component {
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = { isWork: true, day: '星期五' }
            }
            render() {
                let { isWork, day } = this.state
                return <h1 id="title" onClick={demo}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Demo />, document.getElementById('app'))

        function demo() {
            console.log("标题被点击了")
        }
    </script>

再次保存刷新,查看效果。

在这里插入图片描述

OK。现在一切都正常了,可以实现点击之后再控制台输出文本。这样点击事件就完成了。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

this 指向问题

上面部分我们实现了 React 绑定点击事件,接下来就是改变 isWork 的值就可以了。

首先为了标准我们把之前的代码修改一下:

    <script type="text/babel">
        // 1. 创建类式组件
        class Work extends React.Component {
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = { isWork: true, day: '星期五' }
            }
            render() {
                let { isWork, day } = this.state
                return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }
            changeWork() {
                console.log("标签被点击了")
            }
        }
        // 2. 渲染组件到页面
        ReactDOM.render(<Work />, document.getElementById('app'))
    </script>

下面的代码案例,都基于上面x修改后的代码继续编写,逻辑没改,就修改了几个名称。 但是有一点要注意,我们把 changeWork 移入 Work 对象里面了,所以说 他在 Work 的原型对象上,供实例使用。通过 Work 实例调用 changeWork 时候,changeWork 中的 this 就是 weather 实例。注意这一点儿。

有很多小伙伴觉得,OK,后边的我熟啊!直接在点击方法里面写逻辑就可以了。比如先在点击方法里面打印一下 isWork 的值。

    <script type="text/babel">
        // 1. 创建类式组件
        class Work extends React.Component {
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = { isWork: true, day: '星期五' }
            }
            render() {
                let { isWork, day } = this.state
                return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }
            changeWork() {
                console.log(this.state.isWork)
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Work />, document.getElementById('app'))
    </script>

保存刷新,点击标签,看一下打印的数据。 在这里插入图片描述 好家伙,我们控制台直接报错了。说找不到 isWork。不应该啊,我们直接打印一下 this

在这里插入图片描述 我们看到,打印出的 this 不是 Work 实例。这就纳闷了,为啥 构造器函数,render 函数中的 this 是 Work 实例,但是到了 changeWork 函数就不是了? 明明都在同一级的!

要想明白这里,我们先去看一下 类的 this 指向问题。

类的 this 指向问题

看下面代码:

<script type="text/javascript">
    class Boy {
      constructor(name, age) {
        this.name = name
        this.age = age
      }
      speak() {
        console.log(this)
      }
    }

    const b1 = new Boy('我是ed.', 10)
    b1.speak()  //  通过实例调用

  </script>

上面的代码我们创建了一个 boy 类,里面有一个 speak 方法,然后打印的是 speak 里面的 this。

然后我们实例化一下,然后调用一下 speak,看一下 this 指向是谁!

在这里插入图片描述 我们看到 this 的指向就是 Boy 的实例。

那我们修改一下代码:

<script type="text/javascript">
    class Boy {
      constructor(name, age) {
        this.name = name
        this.age = age
      }
      speak() {
        console.log(this)
      }
    }

    const b1 = new Boy('我是ed.', 10)
    b1.speak()  //  通过实例调用

    const x  = b1.speak
    x()

  </script>

上面的代码,我们把 b1 的 speak 方法赋值给了 x,也就是说 x 就是 speak 方法,我们调用 x ,看一下 this 打印的是啥。

在这里插入图片描述

x 打印出来是 undefined。为什么!因为哈,speak 函数 放在了类的原型对象上,他是给实例使用的,通过 Boy 实例调用 speak 的时候,speak 中的 this 就是 Boy 实例。这就是第一个能打印的原因。那第二个打印是 undefined 的,是因为,尽管给 x 赋值为 speak 函数,但是调用它的,已经不是 Boy 实例了,所以 speak 中的 this 是 undefined,他只能在往上一层找 window ,but!类中所有定义的方法,已经默认开启了严格模式,所以不会指向window,只能停止,打印 undefined。

思路转回案例

OK。我们回到上面那个打印 isWork 的是 undefined 的地方,为什么点击标签,他的 this 不是 Work 的实例对象? 这个地方和这个 x 的原因一模一样!!!

看下面代码:

render() {
      let { isWork, day } = this.state
      return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}

changeWork() {
     console.log(this.state.isWork)
}

我们的 changeWork 事件,是在 Work 的原型对象上,供实例使用。但是在标签上 <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1> 这句代码上,我们把 changeWork 赋值给了 onClick 的回调,所以不是通过实例调用的,是直接调用。所以说,this 的指向变了,不是 Work 实例了,所以直接去获得 isWork 的是后是 undefined。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

this 问题解决

上面我们弄清楚了 this 指向改变的问题,怎么解决?

so easy!

使用 bind

bind 的作用:bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的 this 对象的值会被绑定到传入 bind() 第一个参数的值,当然这是绑定,不是执行。

    <script type="text/babel">
        // 1. 创建类式组件
        class Work extends React.Component {
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = { isWork: true, day: '星期五' }
                // 解决 changeWork 中 this 指向问题
                this.changeWork = this.changeWork.bind(this)
            }
            render() {
                let { isWork, day } = this.state
                return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }
            changeWork() {
                // changeWork 放在哪里?—— Work 的原型对象上,供实例使用
                // 由于 changeWork 是作为 onClick 的回调,所以不是通过实例调用的,是直接调用
                // 类中的方法默认开启了局部的严格模式,所以 changeWork 中的 this 为 undefined
                console.log(this.state.isWork)
            }
        }
        // 2. 渲染组件到页面
        ReactDOM.render(<Work />, document.getElementById('app'))
    </script>

OK,实现完成!

在这里插入图片描述

所以说我们点击标签的 changeWork 是自身的 changeWork , 不是原型的 changeWork ,但是 自身的 changeWork 是通过 原型的 changeWork 生成的,只不过把 this 指向改掉了,目的是让点击事件可以获取想要的 this 指向。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

setState 的使用

this 指向问题解决了,可以直接获取到 isWork 状态了。然后就是点击标签,实现 isWork 状态的切换。

这个简单啊,无非就是点击标签的时候,isWork!isWork 呗!

		changeWork() {
                // 获取原来的 isWork 值
                const isWork = this.state.isWork
                // 注意:状态 state 不可以直接更改,必须借助api修改
                this.state.isWork = !isWork 
                let day = isWork?'星期天':'星期五'
                console.log(this.state.isWork)
            }

保存,刷新,看效果。

在这里插入图片描述

为什么!宽松点说,就是你改了,但是界面没有刷新,严格点说,写的不对。

因为 state 提供了 api 用来修改 state 状态,就是 serState

    <script type="text/babel">
        // 1. 创建类式组件
        class Work extends React.Component {
            
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = { isWork: true, day: '星期五' }
                // 解决 changeWork 中 this 指向问题
                this.changeWork = this.changeWork.bind(this)
            }
            
            render() {
                let { isWork, day } = this.state
                return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }
            
            changeWork() {
                // 获取原来的 isWork 值
                const isWork = this.state.isWork
                // 注意:状态 state 不可以直接更改,必须借助api修改
                this.state.isWork = !isWork  // 这是错误的写法
                let day = isWork?'星期天':'星期五'
                this.setState({ isWork: !isWork, day: day }) // 这是对的,必须使用 setState 修改,修改为合并操作
                console.log(this.state.isWork)
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Work />, document.getElementById('app'))
    </script>

保存刷新,查看效果。

在这里插入图片描述 OK, 效果完成!非常 nice!!!

这部分最后想一个东西,我们的 构造体函数,在实现这个功能的时候执行了几次啊?对,一次,就是在实例化的时候执行的一次。那 render 函数执行了几次? 是的, n + 1 次,这个1 是页面刷新出来的一次,n 是点击标签的次数。那 changeWork 函数执行了几次呀? 嗯,执行了 n 次,点一次标签,执行一次。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

state 简写

上边逼逼赖赖了那么就,就为了实现一个小小的功能,写了一页代码,能看到这儿的应该很少了吧。

关于 state 简写的很简单,省好多代码量,也不用解决 this 指向问题。

下面稍微说一下。

我们先说一下普通类,类中可以包含什么?

  • 构造体函数 constructor(){ }
  • 方法 demo(){}
  • 赋值语句 a=1

其中赋值语句 a = 1 的含义是:给 Work 的实例对象添加一个属性,命名为a,值为1

那我们的仔细看上一部分最后实现的代码,上面代码的构造体函数里面实现了什么功能啊?是不是只是初始化 state 状态 和 解决 this 指向问题,既然类中可以直接写赋值语句,那我们就可以把 state 直接提出来啊!

<script type="text/babel">

        class Work extends React.Component {

            constructor(props) {
                 super(props)
                 // this.state = { isWork: true, day: '星期五' }
                 this.changeWork = this.changeWork.bind(this)
           }

            // 初始化状态
            state = { isWork: true, day: '星期五' }

            render() {
                let { isWork, day } = this.state
                return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }

            // 自定义方法
            changeWork() {
                // 获取原来的 isWork 值
                const isWork = this.state.isWork
                // 注意:状态 state 不可以直接更改,必须借助api修改
                this.state.isWork = !isWork  // 这是错误的写法
                let day = isWork?'星期天':'星期五'
                this.setState({ isWork: !isWork, day: day }) // 这是对的,必须使用 setState 修改,修改为合并操作
                console.log(this.state.isWork)
            }
        }

        ReactDOM.render(<Work />, document.getElementById('app'))
        
    </script>

上面代码保存,刷新,也是可以正常执行的。

然后现在 构造函数 中就只有 this 指向问题的解决代码了,那 this 指向问题有没有其他的解决办法?

有!

箭头函数!

先看下面代码,我们把 changeWork 改一下:

changeWork = function() {
     const isWork = this.state.isWork
     let day = isWork ? '星期天' : '星期五'
     this.setState({ isWork: !isWork, day: day })
}

改成这样,是什么意思?这就不是一个方法了! 变成赋值语句了!Work原型就没有changeWork方法了,而是在 Work 创建的实例他自身添加了一个 changeWork 属性。

但是仅仅这样不行,因为尽管换成了 赋值语句,但是依旧没有解决 this 指向问题,只不过是换了个位置而已,把原型上的方法变成了自己身上的属性,只是把问题转移了,但是实际上并没有解决。

重点来咯!

再改!

changeWork = () => {
     const isWork = this.state.isWork
     let day = isWork ? '星期天' : '星期五'
     this.setState({ isWork: !isWork, day: day })
}

我们把之前赋值给实例自身 changeWork 属性的 方法,修改成箭头函数。这样就可以了,this 问题解决!

为什么会解决!

因为,箭头函数有一个很大的特点,多大? 超级超级大!那个特点就是 没有自己的 this。

但是,如果你在箭头函数中,就是使用了 this 这个关键字!他不会报错!他会自动找寻外层的 this 作为自己的 this 去使用。

所以说,太他妈的秒~了

他找的外层的 this,正好就是 Work 实例的 this,正好解决了 this 只想问题,我勒个去!棒!

所以说 changeWork 修改成箭头函数之后,就没有了 this 指向问题,构造体函数直接下班!

<!-- 此处必须写 text/babel -->
    <script type="text/babel">

        class Work extends React.Component {

            // constructor(props) {
            //     super(props)
            //     // this.state = { isWork: true, day: '星期五' }
            //     // this.changeWork = this.changeWork.bind(this)
            // }

            // 初始化状态
            state = { isWork: true, day: '星期五' }

            render() {
                let { isWork, day } = this.state
                return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
            }

            // 自定义方法 —— 要用赋值语句的形式 + 箭头函数
            changeWork = () => {
                const isWork = this.state.isWork
                let day = isWork ? '星期天' : '星期五'
                this.setState({ isWork: !isWork, day: day })
            }
        }

        ReactDOM.render(<Work />, document.getElementById('app'))
    </script>

保存,刷新,查看!

在这里插入图片描述

OK,state 简化处理,完成!

总结

理解:

  • state 是组件对象最重要的属性,值是对象(可以包含多个键值对)
  • 组件被称为状态机,通过更新组件的 state 来更新对应的页面显示(重新渲染)

强烈注意:

  • 组件中的 render 方法中的 this 为组件实例对象。
  • 组件自定义的方法中 this 为 undefined,如何解决? 强制绑定this ,通过函数对象的 bind() 箭头函数
  • 状态数据不能直接修改或更新

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

今天的内容就到这里了,东西有点多,能看到这里的几乎没人了吧?加油!宝子们,晚安~

在这里插入图片描述