React组件实例的三大核心属性

316 阅读6分钟

组件的三大核心属性

1:state

1)基础版本的类初始化状态

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>组件绑定事件</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>

<div id="example">

</div>
<script type="text/babel">
  // 1: 创建类式组件
  class MyComponent extends React.Component {
    constructor(props) {
      super(props)
      console.log('this==',this)
      this.state = {isFlag:true}
      this.handleFn = this.handleFn.bind(this)
    }

    render() {
      let {isFlag} = this.state
      return <h1 onClick={this.handleFn}>我是类式组件:{isFlag ? '状态值为真':'状态值为假'}</h1>
    }
    handleFn() {
     let {isFlag} = this.state
     this.setState({
      isFlag: !isFlag
     })
    }
  }

  ReactDOM.render(
    <MyComponent/>,
    document.getElementById('example')
  );

  function handleFn () {

  }
</script>

</body>
</html>

2)简约版本的类初始化状态

这样就能实现状态的更改了,但是一般在真实的项目开发中,不应该使用构造函数的形式初始化类,我们可以通过在类中写赋值语句达到初始化的效果。

更改代码如下:

   class MyComponent extends React.Component {
    // constructor(props) {
    //   super(props)
    //   console.log('this==',this)
    //   this.state = {isFlag:true}
    // }

    // 可以不使用构造函数,而是在类中写赋值语句,值在实例本身,注意不是类的原型上
    state = {isFlag:true}
    render() {
      let {isFlag} = this.state
      return <h1 onClick={this.handleFn}>我是类式组件:{isFlag ? '状态值为真':'状态值为假'}</h1>
    }

    // 定义函数
    handleFn = ()=> {
      // handleFn放在MyComponent的原型对象上,供实例使用
      // 由于handleFn是作为onClick的回调,而不是通过实例调用的,所以是直接调用
      // 类中的方法开启了严格模式,改变了this指向,所以handleFn中的this为undefined

      // 解决办法是:使用箭头函数
      let {isFlag} = this.state
      this.setState({
        isFlag: !isFlag
      })
    }
  }

注意

  • 为了初始化类的状态,我们使用了构造函数的方式,即基础版的方式。
 constructor(props) {
      super(props)
      console.log('this==',this)
      this.state = {isFlag:true}
      this.handleFn = this.handleFn.bind(this)
    }

但定义类状态的时候,有些值不是从外部传进去的,那么此时我们可以不需要构造函数。 即不是从外部传进去的参数,我们可以不用定义在构造函数中。由于isFlag不是从外部传递进去的,我们可以写在构造函数外部:

 // 可以不使用构造函数,而是在类中写赋值语句,值在实例本身,注意不是类的原型上
    state = {isFlag:true}
  • 我们在类中定义的函数,都是作为事件回调来使用的。

自定义的函数一般都是作为回调函数使用的,由于类使用的是严格模式,改变类this,即this = undefined;又由于类中自定义的函数是挂在在类的原型上,供类的实例调用;就是所只有类的实例才能调用自定义的函数,而此时this = undefined不是类的实例,所以我们需要在构造函数中手动改变this的指向,所以就有了如下代码:

constructor(props) {
      super(props)
      console.log('this==',this)
      this.state = {isFlag:true}
      this.handleFn = this.handleFn.bind(this) // 改变this指向问题
    }
handleFn() {
     let {isFlag} = this.state
     this.setState({
      isFlag: !isFlag
     })
    }

通过下图可以看出,handleFn函数在类的原型上的

就是说,我们自定义的函数要想能够供类的实例使用,这些函数都需要通过bind改变 this指向问题。那么问题来了,假如我们有100个自定义函数,这100个函数要想被类的实例对象使用,那么它们都改变this指向问题,这样太麻烦了,于是就有了如下精简办法。

handleFn = ()=> {
     let {isFlag} = this.state
     this.setState({
      isFlag: !isFlag
     })
    }

通过下图可以看出,handleFn函数不在类的原型上的,而是在类的自身

由于this = undefined问题,我们使用了bind的方式改变了this指向问题,我们最终就是想拿得到类的实例对象,那么什么方式可以拿到this呢?箭头函数可以拿到它所处环境的this;为什么自定义函数要用赋值语句的方式呢?那是为了能够使用箭头函数才用的,因为直接定义函数的方式不能够使用箭头函数,即:

handleFn ()=> {} // 这是错误的写法,不存在这种写法

2:props

1)基础版本

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>props</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>

<div id="example">

</div>
<div id="example2">

</div>
<script type="text/babel">
let listData1 = {
  name: '张三',
  age: `20`,
  sex: '男'
}
let listData2 = {
  name: '李四',
  age: 30,
  sex: 76
}

  // 1: 创建类式组件
  class MyComponent extends React.Component {
    render() {
      return (
        <ul>
          <li>姓名:{this.props.listData.name}</li>
          <li>年龄:{this.props.listData.age}</li>
          <li>性别:{this.props.listData.sex}</li>
        </ul>
      )
    }
  }
  ReactDOM.render(
    <MyComponent listData={listData1}/>,
    document.getElementById('example')
  );
  ReactDOM.render(
    <MyComponent listData={listData2}/>,
    document.getElementById('example2')
  );
</script>

</body>
</html>

2:props传参数类型的限制以及默认值

1)基础版
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>props类型限制</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
</head>
<body>

<div id="example">

</div>
<div id="example2">

</div>
<script type="text/babel">
let listData1 = {
  name: '张三',
  age: 20,
  sex: '男'
}
let listData2 = {
  name: '李四',
  age: 30,
  sex: '女'
}

  // 1: 创建类式组件
  class MyComponent extends React.Component {
    render() {
      return (
        <ul>
          <li>姓名:{this.props.name}</li>
          <li>年龄:{this.props.age}</li>
          <li>性别:{this.props.sex}</li>
        </ul>
      )
    }
  }

  // 标签必要性
  MyComponent.propTypes = {
    name: PropTypes.string.isRequired,
    sex: PropTypes.string,
    age: PropTypes.number,
    speak: PropTypes.func
};

// 执行默认值
MyComponent.defaultProps = {
    age: 20,
};
  ReactDOM.render(
    <MyComponent {...listData1}/>,
    document.getElementById('example')
  );
  ReactDOM.render(
    <MyComponent  {...listData2}/>,
    document.getElementById('example2')
  );
</script>

</body>
</html>

分析:

<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
  • 需要使用{...objData}进行展开,这是React提供的,这可不是扩展运算符哦,因为我们知道在js对象不能够使用扩展运算符,只有数组才有。本例中如果不使用{...listData2},那么限制条件和默认值就不起作用。
  • propTypes React自定义组件提供的属性,而PropTypes是我们引入prop-types.js库才有的。
2)精简版

我们定义涉及到类自身的,最好都是包含在类中,而不是单独写出来,不好统一维护,于是就有如下精简的代码:

<script type="text/babel">
let listData1 = {
  name: '张三',
  age: 20,
  sex: '男'
}
let listData2 = {
  name: '李四',
  age: 30,
  sex: '女'
}

  // 1: 创建类式组件
  class MyComponent extends React.Component {
    constructor(props) {

      // super传不传参数,取决于是否在构造函数中通过this访问props
      // 但在实际开发中不会这么用,我们可以直接使用传递过来的props,所以也证实了类一般很少写构造函数

      // 第一种情况super(props)传参数
      // super(props)

      // 第二种情况super()不传递参数
      super()
      console.log('constructor中this.props==',this.props) // undefined
      console.log('constructor中props==',props) 
    }

    render() {
      return (
        <ul>
          <li>姓名:{this.props.name}</li>
          <li>年龄:{this.props.age}</li>
          <li>性别:{this.props.sex}</li>
        </ul>
      )
    }
 
    // 标签必要性
   static propTypes = {
      name: PropTypes.string.isRequired,
      sex: PropTypes.string,
      age: PropTypes.number
    };

    // 执行默认值
   static defaultProps = {
      age: 20,
    };
  
  }

  ReactDOM.render(
    <MyComponent {...listData1}/>,
    document.getElementById('example')
  );
  ReactDOM.render(
    <MyComponent  {...listData2}/>,
    document.getElementById('example2')
  );
</script>

分析:

  • 在类中使用赋值语句,我们是把属性添加到类的实例对象上,但propTypesdefaultProps都需要挂在类自身身上,而加上static,可以把属性添加到类本身上,所以可以使用此方式给类添加属性。

  • 是否使用构造函数

    • 实际开发中很少使用。因为就算不使用,基本没什么影响。

    • 如果我们使用了构造函数,super是需要调用的。如果我们super()不传参数的话,在构造函数中this.propsundefined;如果我们super(props)传参数时,在构造函数中this.props就可以有值。但在实际开发中不会这么用,我们可以直接使用传递过来的props,所以也证实了类一般很少写构造函数

        constructor(props) {
    
      // super传不传参数,取决于是否在构造函数中通过this访问props
      // 但在实际开发中不会这么用,我们可以直接使用传递过来的props,所以也证实了类一般很少写构造函数
    
      // 第一种情况super(props)传参数
      // super(props)
      console.log('constructor中this.props==',this.props) // 有值
    
      // 第二种情况super()不传递参数
      super()
      console.log('constructor中this.props==',this.props) // undefined
      console.log('constructor中props==',props) 
    }
    
  • 函数式创建组件可以使用props

<script type="text/babel">
let listData1 = {
 name: '张三',
 age: 20,
 sex: '男'
}
let listData2 = {
 name: '李四',
 age: 30,
 sex: '女'
}

 // 1: 创建函数式组件
 function MyComponent (props) {
   console.log('MyComponent==',this) // undefined
   return (
     <ul>
         <li>姓名:{props.name}</li>
         <li>年龄:{props.age}</li>
         <li>性别:{props.sex}</li>
       </ul>
   )
     
 }

 ReactDOM.render(
   <MyComponent {...listData1}/>,
   document.getElementById('example')
 );
 ReactDOM.render(
   <MyComponent  {...listData2}/>,
   document.getElementById('example2')
 );
</script>

因为函数式组件是没有组件的实例的,即thisundefined,所以staterefs在函数式组件中是不可以使用的,函数式组件之所以能够使用props,是因为函数可以接受参数。

  • props是只读属性,是不可以修改的

3:ref

1)string类型的ref

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
    <title>string类型的ref</title>
</head>
<body>

    <div id="box"></div>

    <script type="text/babel">

        class MyComponent extends React.Component{
            render() {
                return (
                    <div>
                        <input ref="input1" type="text"/>
                        <button onClick={this.showData}>点击按钮</button>
                    </div>
                )
            }

            showData = () => {
                console.log('获取值',this.refs.input1.value)
            }
        }

        ReactDOM.render(<MyComponent/>,document.getElementById('box'))
    </script>
</body>
</html>

React官方不建议使用,因为查询数度慢

2)回调函数类型的ref

<script type="text/babel">

        class MyComponent extends React.Component{
            render() {
                 return (
                     <div>
                          <input ref={(currentNode) => {this.input1 = currentNode}} type="text"/>
                          <button onClick={this.showData}>点击按钮</button>
                     </div>
                 )
            }

            showData = () => {
                // console.log('获取值1',this.input1.value)
            }
        }

        ReactDOM.render(<MyComponent/>,document.getElementById('box'))
    </script>

注意:

  • ref里面的回调函数不能是普通函数,普通函数thisundefined,应该是箭头函数。

  • 内联函数的ref调用几次函数呢?第一次初始化的时候调用一次。组件更新的时候调用了两次,第一次回调函数接收的是null,第二次才是节点内容

<script type="text/babel">

        class MyComponent extends React.Component{
            state = {isFlag:false}
            render() {
                return (
                    <div>
                        <h1>{this.state.isFlag ? '我是真值':'我是假值'}</h1>
                         <input ref={(currentNode) => {this.input1 = currentNode;console.log('@==',currentNode)}} type="text"/>
                        <button onClick={this.showData}>点击按钮</button>
                        <button onClick={this.changeData}>更新组件</button>
                    </div>
                )
            }

            showData = () => {
                console.log('获取值',this.input1.value)
            }
            changeData = () => {
                let {isFlag} = this.state
                this.setState({
                    isFlag: !isFlag
                })
            }
        }

        ReactDOM.render(<MyComponent/>,document.getElementById('box'))
    </script>

分析:

只要state里面的状态值发生了改变,就会触发组件的更新,那么就会触发回调函数。即只有组件更新的时候,才会触发两次。

第一次初始化组件的时候:

组件更新的时候:

组件更新的时候,回调函数为什么回调两次?

这是React里面设置的,第一次起到清空的作用,第二次给新的函数,注意,是新的回调函数了。就是说初始化的时候,执行了一次回调函数。组件更新的时候,先把之前的回调函数清空,然后再执行新的回调函数。

解决的办法:

把回调函数定在组件实例的身上,这样每次都不会清空,而是从组件的实例上获取回调函数。

<script type="text/babel">

        class MyComponent extends React.Component{
            state = {isFlag:false}
            render() {
                return (
                    <div>
                        <h1>{this.state.isFlag ? '我是真值':'我是假值'}</h1>
                         <input ref={this.currentFn} type="text"/>
                         <button onClick={this.showData}>点击按钮</button>
                        <button onClick={this.changeData}>更新组件</button>
                    </div>
                )
            }
            currentFn = (currentNopde) => {
                console.log('@==',currentNopde)
                this.input1 = currentNopde
            }
            showData = () => {
                console.log('获取值',this.input1.value)
            }
            changeData = () => {
                let {isFlag} = this.state
                this.setState({
                    isFlag: !isFlag
                })
            }
        }

        ReactDOM.render(<MyComponent/>,document.getElementById('box'))
    </script>

分析:

<input ref={this.currentFn} type="text"/>

--------------------------------------
currentFn = (currentNopde) => {
      console.log('@==',currentNopde)
      this.input1 = currentNopde
  }

3)createRef类型的ref

<script type="text/babel">

        class MyComponent extends React.Component{
            state = {isFlag:false}
            render() {
                return (
                    <div>
                         <input ref={this.currentFn} type="text"/>
                         <button onClick={this.showData}>点击按钮</button>
                    </div>
                )
            }

            currentFn = React.createRef()
            
            showData = () => {
                console.log('@=',this.currentFn.current.value)
            }
        }

        ReactDOM.render(<MyComponent/>,document.getElementById('box'))
    </script>