40. B站 react三大属性1:state - yiqunkeke/react-jianshu-shangguigu-zty GitHub Wiki

1. 原生js

document.createElement ----> 创建真实DOM

React.createElement ---> 创建虚拟DOM

   const VDOM = React.createElement('h1', { id: 'title'}, 'hello, react')

   const VDOM2 = React.createElement('h1', { id: 'title'}, React.createElement('span', {}, 'hello, react'))

2. jsx

是原生js创建的语法糖

   const VOM = (
     <h1>
         <span>hello, react</span>
    </h1>
   )

3. 关于虚拟DOM:

  • 本质是Object类型的对象(一般对象)
  • 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是 Reac内部在用,无需真实DOM上那么多的属性
  • 虚拟DOM最终会被 React转化为真实DOM,呈现在页面上。

4. jsx语法规则

  • XML 早期 用于存储和传输数据
<student>
   <name>Tom</name>
   <age>19</age>
</student>

后来这种方式逐渐被JSON替换。原因是,xml存储数据的方式,结构代码比数据代码居然占的还要多。

  • JSON
"{"name": "TOM", "age": 19}"

用JSON存储数据,更一目了然,而且JSON提供了2个非常好用的API. JSON.parse()JSON.stringfy()

  • jsx语法规则

    1. 样式的类名指定不要用class,要用className
      <style>
        .title { background: yellow}
      </style>
      const VOM = (
         <h2 className="title">hello,react</h2>
      )
    1. 标签中混入 JS表达式 要用 {}

    2. style内联样式使用双{{}},属性名小驼峰命名

     const VOM = (
         <h2 style={{color: 'white', fontSize: '29px'}}>hello,react</h2>
      )
    1. 只能有一个根标签
     const VOM = (
        <div>
         <h2 style={{color: 'white', fontSize: '29px'}}>hello,react</h2>
         <input type="text"/>
        </div>
      )
    1. 标签必须闭合 <input>错误 <input/>正确

    2. 关于标签首字母

      1. 若小写字母开头,则将该标签转为html中同名元素;若html中无该标签对应的同名元素,则报错。
      2. 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
    3. { } 中只能写表达式。 一定区分 【js语句】和 【js表达式】

      1. 表达式:一个表达式会产生一个值,可以主在任何王玲上需要值的地方。
         // 下面这些都是表达式
         a
         a+b
         demo(1) // 函数调用表达式
         arr.map() // map用于加工数组,返回一个新数组
         function test() {}
      1. 语句(代码)
        // 下面这些都是语句
        if() {}
        for() {}
        switch() { case: xxx }
    4. 所以在{ }中不能写for循环,不能写if..else,不能写switch..case

     <script type="text/babel">
       // 模拟一些数据
       const data = ['Angular', 'React', 'Vue']
    
      // 1.创建虚拟DOM
       const VDOM = (
         <div>
              <h1>前端js框架列表</h1>
              <ul>
                  {
                     for()  // 报错
                   }
              </ul>
         </div> 
       ) 
    
      // 2.渲染虚拟DOM到页面
      ReactDOM.render(VDOM, document.getElementById('test'))
    </script>

    在{ }中只能写表达式

     <script type="text/babel">
       // 模拟一些数据
       const data = ['Angular', 'React', 'Vue']
    
      // 1.创建虚拟DOM
       const VDOM = (
         <div>
              <h1>前端js框架列表</h1>
              <ul>
                  {
                     data.map((item, index) => {
                       return <li key={index}> {item} </li>
                     })
                   }
              </ul>
         </div> 
       ) 
    
      // 2.渲染虚拟DOM到页面
      ReactDOM.render(VDOM, document.getElementById('test'))
    </script>

    并且,如果你在{}中写了一个数组,react会帮你把数组自动进行遍历。但如果你写一个对象,react就无能为力了。

5. 模块与组件、模块化与组件化

模块

  • 理解:向外提供特定功能的js程序,一般就是一个js文件。
  • 为什么要拆成模块: 随着业务逻辑增加,代码越来越多且复杂
  • 作用: 复用js,简化js的编写,提高js运行效率

组件

  • 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
  • 为什么:一个界面的功能更复杂
  • 作用: 复用编码,简化项目编码,提高运行效率

模块化

  • 当应用的js都以模块来编写的,这个应用就是一个模块化的应用

组件化

  • 当应用是以多组件 的方式实现,这个应用就是一个组件化的应用

6.React面向组件编程

6.1. 函数式组件

回顾组件的定义,是代码和资源的集合,包含 html, css, js, font, video...等等

总结:组件最少也得有结构

      <script type="text/babel">
        // 1. 创建函数式组件
        // function demo() {
        //  return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
        // }

        function Demo() {
           console.log(this) // 此处的this是undefined,因为babel编译后开启了严格模式
           return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
         }
    
        // 2. 渲染组件到页面
        // ReactDOM.render(demo, document.getElementById('test'))
         // 报错:Warning: Functions are not valid as a React child.(函数类型不能做为 react 的节点)This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.

        // 要写demo标签
        // ReactDOM.render(<demo/>, document.getElementById('test'))
       // 报错:Warning: The tag <demo> is unrecognized in this browser. (浏览器不识别demo元素)If you meant to render a React component, start its name with an uppercase letter.(组件首字母需大写)
   
        ReactDOM.render(<Demo/>, document.getElementById('test'))
     </script> 

​ 首字母大写、闭合、组件写成标签。

​ 函数式组件,函数名就是组件名。

​ 执行了 ReactDOM.render(, document.getElementById('test'))之后,发生了什么?

  • React 解析组件标签,找到了 组件
  • 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中

6.2. 类式组件--复习类的基本知识

    <script type="text/javascript">
       // 创建一个Person类
       class Person {
           // 构造器方法
           constructor(name, age) {
               // 构造器中的this是谁? ---类的实例对象
               this.name = name
               this.age = age
           } 
           // 一般方法
           speak() {
                // speak方法放在了哪里?----类的原型对象上,供实例调用
                // 通过Person实例调用speak时,speak中的this就是Person实例
                console.log(`我叫${this.name},年龄是${this.age}`)
           } 
       }
       // 创建一个Person的实例对象
       const p1 = new Person('tom', 18)
       const p2 = new Person('jerry',19)
       console.log(p1)   //  Person {name: 'tom', age: 18}
       console.log(p2)   //  Person {name: 'jerry', age: 19}
       p1.speak()
       p2.speak()
    </script>    

总结 :

  1. 类中的构造器不是必须写的。要对实例进行一些初始化的操作,如添加指定属性时才写。

  2. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。

  3. 类中的定义的方法,都是放在类的原型对象上,供实例使用。

6.2.3. 类式组件

类式组件,类名就是组件名

<script type="text/babel">
    // 1.创建类式组件
    class MyComponent extends React.Component{
       render() {
          // render()是放在哪里的?----MyComponent的原型对象上,供实例使用。问题:实例在哪呢?
          // render中的this是谁? -----MyComponent这个类的实例对象。<=>或者叫 MyComponent组件的实例对象。简称 MyComponent组件实例对象
          // 注意 render中的this指向的不是MyComponent类,而是指向通过MyComponent类new出来的实例对象。即组件实例对象。
          console.log('render中的this': this)  // MyComponnet {....} 
          return <h2>我是用类定义的组件,(适用于【复杂组件】的定义)</h2>
       }
    }
    // 2.渲染组件到页面
    ReactDOM.render(<MyComponent/>, document.getElementById('test'))
    // 当你执行了ReactDOM.render(<MyComponent/>, document.getElementById('test'))之后,发生了什么?
    //    1. React解析组件标签,找到了MyComponent组件
    //    2. 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
    //    3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
</script>

7. 对 state的理解

  1. 【复杂组件】--有状态的组件叫复杂组件。状态,就是 state。

  2. [简单组件]--无状态的组件

  3. 状态

    人 ----> 状态 ---> 影响 ---> 行为

    组件 ----> 状态 ----> 驱动 ---> 页面

8. 组件实例的三大属性1: state

注意这里是【组件实例】的三大属性。由于函数式组件中,并没有实例对象。所以这里讲的3大属性,都是针对类式组件的。

类式组件中是有 组件实例对象的。我们讲的3大属性就是组件实例对象中的属性。

   <script type="text/babel">
       // 1. 创建组件
       class Weather extends React.Component {
          // 写构造器。
          // 构造器能接收到什么?取决于new Weather时传的什么。
          // 可是,new Weather这个动作,是由React帮我们做的。---->只能去参考官方文档。
          // 官网上首页-有状态组件--代码:构造器能接收到props
          constructor(props) {
             super(props) // 在类中,如果该类继承自,且写了constructor,则必须调一下super。
             // 如果不调用super,则报错:missing Super() call in construtor
             
             // 写构造器的目的是什么?在类的实例对象上添加一些属性 
             this.state = {isHot: true}
          }
           
          render() {
             console.log(this)  // this 就是Weather类的实例对象【Weather的组件实例对象】。如果你想在类的实例对象上添加一些属性,就需要借助constructor构造器
             const { isHot } = this.state
             return <h1>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
          }
       }
  
       // 2. 渲染组件到页面 
       ReactDOM.render(<Weather/>, document.getElementById('test'))
   </script>

总结:上面就是借助构造器初始化状态。以及读取组件实例对象中的状态。

9. 复习:原生事件绑定

  <body>
       <button id="btn1">按钮1</button>
       <button id="btn2">按钮2</button>
       <button onclick="demo()">按钮3</button>
       <script type="text/javascript">
            // 方式1
            const btn1 = document.getElementById('btn1')
            btn1.addEventListener('click', () => {
                alert('按钮1被点击了')
            })
            
            // 方式2
            const btn2 = document.getElementById('btn2')
            btn2.onclick = () => {
                alert('按钮2被点击了')
            }

            // 方式3
            function demo() {
               alert('按钮3被点击了')
            }

       </script>
  </body>

在React中,上面3种方式都可以用。但是更推荐第三种。

     <script type="text/babel">
         class Weather extends React.Component {
            constructor(props) {
               super(props) 
               this.state = {isHot: true}
            }
             
            render() { 
               const { isHot } = this.state
               return <h1 id="title">今天天气很{isHot ? '炎热' : '凉爽'}</h1>
            }
         }

         ReactDOM.render(<Weather/>, document.getElementById('test'))
         
         // 方式1
         // const title = document.getElementById('title')  // 获取节点 
         // title.addEventListener('click', () => {  // 绑定事件
         //    console.log('标题被点击了')
         // })

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

         // 虽然这两种方式都可以实现事件绑定。但是尽量不这么用。这种满屏都是document操作。

     </script>

在react中,尽量用第三种方式

     <script type="text/babel">
         class Weather extends React.Component {
            constructor(props) {
               super(props) 
               this.state = {isHot: true}
            }
             
            render() { 
               const { isHot } = this.state
               return <h1 onclick="demo()">今天天气很{isHot ? '炎热' : '凉爽'}</h1>
            }
         }

         ReactDOM.render(<Weather/>, document.getElementById('test'))
         
         function demo() {
            console.log('标题被点击了')
         }
         
         // 报错了: Invalid handler property `onclick` Did you mean `onClick` ?
     </script>

在react中,把所有的原生事件进行了重写。把原来的小写,改成大写。如 onclick ---> onClick、 onblur ---> onBlur

     <script type="text/babel">
         class Weather extends React.Component {
            constructor(props) {
               super(props) 
               this.state = {isHot: true}
            }
             
            render() { 
               const { isHot } = this.state
               return <h1 onClick="demo()">今天天气很{isHot ? '炎热' : '凉爽'}</h1>
            }
         }

         ReactDOM.render(<Weather/>, document.getElementById('test'))
         
         function demo() {
            console.log('标题被点击了')
         }
         
         // 报错了: Expected `onClick` listener to be a function, instead got a value of `string` type。(onClick事件的监听必须是一个函数,不能是一个字符串)
     </script>

在react中,事件绑定后面必须是一个函数。

     <script type="text/babel">
         class Weather extends React.Component {
            constructor(props) {
               super(props) 
               this.state = {isHot: true}
            }
             
            render() { 
               const { isHot } = this.state
               return <h1 onClick={demo()}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
            }
         }

         ReactDOM.render(<Weather/>, document.getElementById('test'))
         
         function demo() {
            console.log('标题被点击了')
         }
         
         // 有问题:现在还没点击 ,控制台却出现'标题被点击了'。为什么?
         
     </script>

React在渲染组件的时候,帮你new了一个Weather组件实例。通过实例调用到了render。 想拿到render返回值就会执行<h1 onClick={demo()}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>代码。所以demo()会被 执行。因为我们onClick={demo()}这样写的{ }中是一个函数调用表达式。即我们把demo()函数的返回值赋值给了onClick。demo()函数有返回值,是undefined。

所以刚才有输出是因为我们加了(),导致demo函数被调用了的原因导致的。

     <script type="text/babel">
         class Weather extends React.Component {
            constructor(props) {
               super(props) 
               this.state = {isHot: true}
            }
             
            render() { 
               const { isHot } = this.state
               return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
            }
         }

         ReactDOM.render(<Weather/>, document.getElementById('test'))
         
         function demo() {
            console.log('标题被点击了')
         }
                  
     </script>

10. 类中方法中的this 存在的问题 ----------------------------------------------------------- 【重点】

需求:点击标题时,修改state中值

   <script type="text/babel">
       // 自定义类式组件
       class Weather extends React.Component {
          constructor(props) {
             super(props) 
             this.state = {isHot: true}
          }
           
          render() { 
             const { isHot } = this.state
             return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
          }
       }
       // 渲染组件
       ReactDOM.render(<Weather/>, document.getElementById('test'))
       
       // 自定义函数
       function demo() {
          // console.log('此处修改isHot的值')
          // 拿到isHot的值直接修改不就完事了么?
          const { isHot } = this.state
          console.log(isHot) 
       }

       // 报错:Cannot read property 'state' of undefined(不能够读取state,在undefined身上)
       // 所以说是xx.state的左侧xx出问题了。所以this出问题了。
       
       // 注意,demo是自定义函数,babel禁止自定义中的this指向window。所以babel默认开启了严格模式。this指向了undefined 。        
   </script>

注意,demo是自定义函数,babel默认开启了严格模式,this指向了undefined 。

 <!--默认this指向window-->
 <script type="text/javascript">
     function demo() {
           // this 指向 window
           console.log(this)
      }
     demo()
 </script>

开启严格模式后,this指向 undefined

 <!--开启严格模式后,this指向 undefined-->
 <script type="text/javascript">
     function demo() {
           'use strict'
           // this 指向 undefined
           console.log(this)
      }
     demo()
 </script>

总结:严格模式下, this指向undefined。非严格模式下,this 指向window。

不管this指向的是undefined还是 window,this下面都没有state属性。所以,上面一开始会报错:Cannot read property 'state' of undefined(不能够读取state,在undefined身上)

   <script type="text/babel">
       class Weather extends React.Component {
          // 构造器中this指向组件实例对象
          constructor(props) {
             super(props) 
             this.state = {isHot: true}
          }
          // render中也可以读取到组件实例对象 
          render() { 
             const { isHot } = this.state
             return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
          }
       }
       
       ReactDOM.render(<Weather/>, document.getElementById('test'))
       
       // 自定义方法
       function demo() {
          // 所以在这个位置,根本就碰不到自定义类式组件中的组件实例对象。
          // 因为Weather类中的实例对象,压根不是你new Weather()出来的,你怎么碰? 你只是写了<Weather/>,React帮你new的实例
          // 怎么解决?
     
       }
 
   </script>

用一种笨方法解决:

   <script type="text/babel">
       let that
       // 定义类组件 
       class Weather extends React.Component {
          constructor(props) {
             super(props) 
             this.state = {isHot: true}
             that = this  // 在构造器中,缓存this
          }

          render() { 
             const { isHot } = this.state
             return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
          }
       }
       
       // 渲染类组件
       ReactDOM.render(<Weather/>, document.getElementById('test'))
       
       // 自定义方法
       function demo() {
         console.log(that)  // 则这里的that肯定是构造器中的this,而构造器中的this又是组件实例对象。所以 that.state.isHot肯定可以取到值。
       }
 
   </script>

停!来观察下代码结构。在react中,希望只有上面两个:定义类组件和渲染类组件。其他相关的都放进定义类的内部。

   <script type="text/babel">
       // 定义类组件 
       class Weather extends React.Component {
          constructor(props) {
             super(props) 
             this.state = {isHot: true}
          }

          render() { 
             const { isHot } = this.state
             return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
          }
          
          // 自定义方法--注意,类不是函数体,在类中定义函数,不用加 function
          // function demo() { 
          //  console.log(that) 
          // }
          
          demo() {
              // demo()放在哪里了?--- Weather类的原型对象上。供实例使用
              // 通过Weather的实例调用demo时,demo中的this就是Weather实例。
              console.log(this.state.isHot)
          }
       }
       
       // 渲染类组件
       ReactDOM.render(<Weather/>, document.getElementById('test'))

       // 报错了:demo is not defined
       // 屡一下:onClick时会调用 demo方法, `onClick={demo}`。那么直接这么写就能够找到类中的demo方法吗?
       // 上面说了,通过Weather的实例调用demo时,demo中的this就是Weather实例。所以必须通过this调用demo才可以访问到demo
       // 所以必须这么写 `onClick={this.demo}`
   </script>
   <script type="text/babel">
       // 定义类组件 
       class Weather extends React.Component {
          constructor(props) {
             super(props) 
             this.state = {isHot: true}
          }

          render() { 
             const { isHot } = this.state
             // 通过Weather的实例调用demo时,demo中的this就是Weather实例。
             return <h1 onClick={this.demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
          }
          
          demo() {
              console.log(this.state.isHot) 
          }
       }
       
       // 渲染类组件
       ReactDOM.render(<Weather/>, document.getElementById('test'))
      
       // 点击,又报错:Cannot read property 'state' of undefined
       // 疑问:为什么constructor、 render中都能够访问到this,或者说都能访问到组件实例。偏偏自定义的demo中就不能呢?
       // 注意这句话:只有通过Weather的实例调用demo时,demo中的this才是Weather实例对象。
       // 因为demo不是通过Weather实例调用的。所以导致this是undefined。对。这是这么回事。凭什么demo就不是实例调用的?onClick={this.demo}这种写法并不是通过实例调用的
 
   </script>

回顾下js中的this丢失问题:

  class Person {
      constructor(name, age) {
         this.name = name
         this.age = age
      }
      speak() {
          // speak放在哪里了?--Person类的原型对象上。供实例调用
         console.log(this)
      }
  }

  const p1 = new Person('tom', 18)
  p1.speak() // 通过实例调用speak方法
  
  const x = p1.speak       // 注意这里:这里赋值语句
  x()    // undefined  这里x()是什么调用。注意,这属于直接调用。不是实例调用。
  
  // 既然x()属于函数直接调用,那么this应该指向window。为什么是undefined?
  // 因为类中定义的方法,开启了局部严格模式。所以说方法中的this没有指向window,而是undefined。
  

所以,回过头来看看

    <script type="text/babel">
        // 定义类组件 
        class Weather extends React.Component {
           constructor(props) {
              super(props) 
              this.state = {isHot: true}
           }

           render() { 
              const { isHot } = this.state
              // 这里的 onClick={this.demo} 写完后,到底有没有调用demo?根本没有调用。
              // 这里只是顺着原型链找到了demo方法,然后把这个函数直接交给onClick作为回调了。
              // 当点击时,直接从堆里面把demo函数,直接调用。所以这里还算是通过实例调用吗?根本不是。
              return <h1 onClick={this.demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
           }
           
           demo() {
               // 由于demo是作为onClick的回调,所以不是通过实例调用的,是直接调用。
               // 类中的方法默认开启了局部严格模式,所以demo中的this为undefined。
               console.log(this.state.isHot) 
           }
        }
        
        // 渲染类组件
        ReactDOM.render(<Weather/>, document.getElementById('test'))
 
    </script>

11. 解决类中方法中的this指向问题--------------------------------------------------------------【重点】

bind(obj)会返回一个新的函数,这个新函数中的this指向obj。

我们想要实现:demo中的this指向Weather的实例对象。只有在demo中拿到了Weather的实例对象。才可以.state.isHot去修改它的值。

   <script type="text/babel">
       // 定义类组件 
       class Weather extends React.Component {
          constructor(props) {
             super(props) 
             this.state = {isHot: true}
             // 一行代码解决 demo中this指向。【重点分析】。下面是赋值语句。
             // 先分析右侧:this.demo.bind中的this是谁?----构造器中的this是组件实例对象。
             // this.demo --- 组件实例对象自身有没有demo?---没有,demo放在了Weather的原型对象上。纵使组件实例对象自身没有demo方法,也会顺着原型找到原型上的demo方法。
             // this.demo.bind 做了两件事情:第一返回一个新的函数,第二,帮你改了新函数中的this,改成了参数中的对象。这里改成了this,this是谁?----构造器中的this是组件实例对象

             // 所以,= 右侧执行完之后的结果是:你得到了一个新函数,而且这个函数里的this已经成功的变为了Weather的实例对象。
             // 分析 = 左侧: 然后,你把这个新函数放到了实例的自身,并且给这个函数起了一个名字,叫 demo.  
             this.demo = this.demo.bind(this) 
          }

          render() { 
             const { isHot } = this.state
             // 问题:当点击 h1时,调用的demo到底是原型链上的 demo还是挂在自身上的demo?   
             // 答案是: 自身上的 demo。   因为按照查找的顺序,自身已经有demo后就不会再去查找原型链。
             return <h1 onClick={this.demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
          }
          
          demo() {
              console.log(this.state.isHot) 
          }
       }
       
       // 渲染类组件
       ReactDOM.render(<Weather/>, document.getElementById('test'))
 
   </script>

12. setState的使用

需求是:获取原来isHot的值,修改

   <script type="text/babel">
       // 定义类组件 
       class Weather extends React.Component {
          
          // 构造器调用几次?---- 1 次(new 了几次就调用几次)
          constructor(props) {
             console.log('constructor')
             super(props) 
             this.state = {isHot: true, wind: '微风'}
             this.demo = this.demo.bind(this) 
          }
          
          // render 调用几次?----- 1 + n 次   1是初始化的那次, n 是状态更新的次数  
          render() { 
             console.log('render')
             const { isHot, wind } = this.state
             return <h1 onClick={this.demo}>今天天气很{isHot ? '炎热' : '凉爽'}{wind}</h1>
          }
          
          // demo 调用几次?----- 点几次调用几次
          demo() {
              console.log('demo')
              // 获取原来的isHot
              const isHot = this.state.isHot
              
              // 严重注意:状态(state) 必须通过setState进行更新,且更新是一种合并,不是替换。 
              this.setState({
                  isHot: !isHot
              }) 
 
              // 严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
              // this.state.isHot = !isHot  // 这是错误的写法
          }
       }
       
       // 渲染类组件
       ReactDOM.render(<Weather/>, document.getElementById('test'))
   </script>

总结:

构造器为什么要写? ------可以不写。写是因为需要在构造器中初始化状态、构造器还能解决this指向问题。

13. setState的简写

上面的代码很标准。但是在真实的开发中,上面的代码可能都不这么写。

我们来分析下代码:

  <script type="text/babel">
      // 定义类组件 
      class Weather extends React.Component {
         
         constructor(props) {    
            super(props) 
            this.state = {isHot: true, wind: '微风'}
            this.demo = this.demo.bind(this) 
         }
         // 对于constructor中的this, 就是该Weather类的实例对象
         
         render() {  
            const { isHot, wind } = this.state
            return <h1 onClick={this.demo}>今天天气很{isHot ? '炎热' : '凉爽'}{wind}</h1>
         }
         // 对于render中的this,也是Weather类的实例对象。
         // 为什么呢?是因为你用了 <Weather/>。React做了这么件事:const w1 = new Weather(),然后通过w1.render调用了render。所以render中的this也是实例对象。
        
         demo() {
             const isHot = this.state.isHot
             this.setState({
                 isHot: !isHot
             }) 
         }
         // 但是在demo中,this指向就出问题了。为什么?因为demo根本就不是w1.demo()调用的。而是作为事件的回调在调用。等你触发了事件之后,它直接把这个demo直接调用
         // 而且类里面的方法开了严格模式,所以this丢了。
         
         demo2() {}

         demo3() {}
         // 如果在类里面又写了自定义的demo2和demo3。你觉得这些方法都是怎么被调用的呢?----绝对不可能是w1.demo2()或者w1.demo3()调用。
         // react不会在new Weather创建出w1实例的时候,再帮你调用w1.demo2()和w1.demo3()。只会调w1.render()。所以你在类中写的自定义方法,基本上都是给事件做回调
         // 使用的。 demo2可能是失去焦点时调用,demo3可能是鼠标划过的时候调用。
 
         // 而如果全部是作为事件的回调在使用,那么在demo2和demo3中的this都会是undefined。如果想解决这个问题,就需要在constructor中写
         // `this.demo1 = this.demo1.bind(this)`、`this.demo2 = this.demo2.bind(this)`...
         // 如果事件回调特别多的情况下,就需要在constructor中写大量的bind代码。
            
         // 问个问题:当初为什么要写构造器?----
         // 由于需要给实例对象进行一些初始化操作,所以需要在constructor中写 this.state = {...}。因为没地方写,所以只能借助构造器来做初始化的事情。
         // 所以,构造器写的很被动。

      }
      
      // 渲染类组件
      ReactDOM.render(<Weather/>, document.getElementById('test'))
  </script>

先来复习下类中的小技巧

 
   class Car {
      constructor(name, price) {
          this.name = name
          this.price = price
          // this.wheel = 4 // 想给每个实例都加一个共同的属性。可以在构造器中这么写。
      }
      
      // 类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个属性,名为a,值为1 
      // a = 1
      wheel = 4  // 但是如果值是new时传递过来的话,就还得需要在constructor中接
   }
 
   const c1 = new Car('奔驰C63', 199)
   const c2 = new Car('宝马', 299)
   console.log(c1)
   console.log(c2)
   // c1和c2上就都有一个wheel属性,值是4

回过头来看

   <script type="text/babel">
       // 定义类组件 
       class Weather extends React.Component {
          
          constructor(props) {    
             super(props) 

             // 同学,我是不是想把所有Weather实例对象身上都追加一个state属性,然后它的值是一个对象。
             // 所以下面这行代码可以不写在构造器中,直接拎到外面
             // this.state = {isHot: true, wind: '微风'}
             
             // 下面这行代码怎么简化?
             // this.demo = this.demo.bind(this) 
          }
          
          // 往Weather的所有实例自身追加一个state属性,值是一个对象。
          state = {isHot: true, wind: '微风'}
           
          render() {  
             const { isHot, wind } = this.state
             return <h1 onClick={this.demo}>今天天气很{isHot ? '炎热' : '凉爽'}{wind}</h1>
          }
         
          // demo放在了哪里?----Weather类的原型对象上。
          // demo() {
          //     const isHot = this.state.isHot
          //     this.setState({
          //         isHot: !isHot
          //     }) 
          // }
          
          // 是不是赋值语句? ----是
          // 这样写:demo放在了哪里? ----放在了实例自身上。Weather的原型上已经没有了 demo
          // demo = function() {
          //     const isHot = this.state.isHot
          //     this.setState({
          //         isHot: !isHot
          //     })
          // }
          // 这么写仅仅是把demo换了一个地方,原来demo在Weather的原型对象上,现在在Weather所缔造的实例对象自身。但这么写并没有解决this指向问题
         
          // 紧接着,我们把普通函数换成箭头函数
          demo = () => {
              console.log(this)  // 确定:这里的this 就是组件实例对象
              const isHot = this.state.isHot
              this.setState({
                  isHot: !isHot
              })
          }
          // 箭头函数有一大特点:没有自己的this。如果在箭头函数中使用了this,它不会报错,而是使用了箭头函数外层的this作为自己的this。
          

       }
       
       // 渲染类组件
       ReactDOM.render(<Weather/>, document.getElementById('test'))
   </script>

所以精简之后的代码是:

   <script type="text/babel">
       // 定义类组件 
       class Weather extends React.Component {
          // 初始化状态
          state = {isHot: true, wind: '微风'}
           
          render() {  
             const { isHot, wind } = this.state
             return <h1 onClick={this.demo}>今天天气很{isHot ? '炎热' : '凉爽'}{wind}</h1>
          }
          
          // 自定义方法 ----- 要用赋值语句 + 箭头函数
          // 以后在类中写方法--都这么写了
          demo = () => {    
              const isHot = this.state.isHot
              this.setState({
                  isHot: !isHot
              })
          }

       }
       
       // 渲染类组件
       ReactDOM.render(<Weather/>, document.getElementById('test'))
   </script>

14. 总结state

  1. state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
  2. 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)

强烈注意:

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

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

    a. 强制绑定 this: 通过函数对象的 bind()

    b. 赋值语句 + 箭头函数

  3. 状态数据,不能直接修改或更新

⚠️ **GitHub.com Fallback** ⚠️