重学 React 之三大属性

2,068 阅读6分钟

上一篇:重学 React 之基础认知

本文 React 用例版本:v17.0.2

React 三大属性是指:

  1. State:来自组件内部的状态
  2. Props:来自外部的属性
  3. Refs:表示组件内的某一个元素

State

state 是一个对象,它包含组件的数据状态,当状态变化时,会触发视图的更新。你可以理解它的作用跟 Vue 中的 data 对象类似。

定义和使用 state

class MyComponent extends React.Component{

  constructor(){
    super()
    this.state = {msg:'hello'} // 定义 state
  }

  render(){
    return (
      <div>
        <h1>{this.state.msg}</h1>
      </div>
    )
  }
  
}

上面代码中,在构造函数中定义了 state 对象,然后在组件中可以使用 this 来获取这个对象及其中的数据。

注意不要跟 Vue 中的使用方法弄混了,在 Vue 的模板里使用 data 数据时是不需要 this 调用的。

state 也可以直接定义在类中,如下:

class MyComponent extends React.Component{

  state = { msg:'hello' }

  render(){
    return (
      <div>
        <h1>{this.state.msg}</h1>
      </div>
    )
  }

}

修改 state

可以通过事件来修改 state 中的数据状态。React 中定义事件主要是在原生事件的基础上把命名改成驼峰命名,比如原生的点击事件 onclick 在 React 中就要写成 onClick

修改 state 例子:

class MyComponent extends React.Component{
  
  state = { msg:"hello" }

  render(){
    return (
      <div>
        <p>{this.state.msg}</p>
        <button onClick={this.changeMsg.bind(this)}>改变</button>
      </div>
    )
  }

  changeMsg (){
    console.log(this.state);
    this.setState({ msg: "你好" });
  }
}

通过定义事件 changeMsg 来修改了 state 中的 msg 数据,可以注意到使用事件时使用 bind 进行了绑定操作,这里解释下原因:

changeMsg 方法是挂载在类的原型上的,如果类的实例直接调用此方法,那么 this 就是指向这个实例,但是触发点击事件时,并不是这个类的实例调用的这个方法,是全局调用的。如果是全局调用的,changeMsg 中的 this 应该指向的是 window,但是这里指向的是 undefined,其主要原因是因为类中定义的方法默认开启了“严格模式”,导致 this 指向了 undefined 了。

我们需要在 changeMsg 方法中获取组件的 state,所以需要主动改变 this 指向到这个类上,所以需要用 bind 绑定 this。

修改 state 中的数据,需要使用 setState 内置方法,这样修改后的数据才能触发视图的更新。setState 是一种合并更新。

事件的 bind 绑定也可以在类组件构造函数里进行,如下:

class MyComponent extends React.Component {
    // 将 state 和 事件绑定移入构造函数中时,一定要先调用 super 方法
    constructor() {
        super();
        this.state = { msg: "hello" };
        this.changeMsg = this.changeMsg.bind(this); // 在这里提前绑定了this,jsx中使用就不需要再绑定了
    }

    render() {
        return (
        <div>
            <p>{this.state.msg}</p>
            <button onClick={this.changeMsg}>改变</button>
        </div>
        );
    }

    changeMsg() {
        console.log(this.state);
        this.setState({ msg: "你好" });
    }
}

关于 setState

这里多说一句 setState

setState 修改状态是一种异步操作,上例中使用的是对象式的使用方法,即第一个参数传入的是一个对象,此时它还可以传入第二个参数,是一个函数,该回调函数是在状态更新,且视图更新完成后调用,可以在这个函数中拿到最新的状态。

setState 还有函数式调用方法,即第一个参数传入一个函数,它的返回值是一个修改状态值的对象。该函数有两个参数,分别是当前组件的 state 和 props。此时 setState 也有一个回调函数,在状态更新,且视图更新完成后调用。

另外 setState 也不一定是同步或者异步的,是根据当前情况来决定的。立个flag,这个后面有时间再详细补充说明。

函数组件无法使用 state

函数组件内部因为没有 this,所以无法使用 state

Props

React 中组件通过 props 属性接收外部传入的数据,这点 Vue 跟 React 是一致的。例子:

<script type="text/babel">
  class MyComponent extends React.Component {
    render() {
      return (
        <ul>
          <li>{this.props.name}</li>
          <li>{this.props.age}</li>
        </ul>
      );
    }
  }
  ReactDOM.render(
    <MyComponent name="Bob" age="18" />,
    document.getElementById("test")
  );
</script>

注意要使用 this 调用 props 属性哦。

如果需要在构造函数中使用 props,必须接收 props 参数,并且传入 super 方法中,像这样:

// ...
constructor(props) {
  super(props); // 这里必须要将接收的 props 作参数传入
  console.log(this.props);
}
// ...

Props 类型检查

如果组件需要传递一个变量或者数字类型,需要加上 {},像这样:

<MyComponent name="Bob" age={18} />

同等情况下的 Vue 中,需要加冒号 : 绑定属性。

有时候我们需要对传入组件的数据进行类型校验,以保证程序的正常运行,此时可以使用一个校验库 prop-types

自 React v15.5 起,React.PropTypes 已移入 prop-types

如果通过 script 标签引入此库,会在 window 上挂载一个 PropTypes (注意首字母大写)变量,此时就可以用这个来校验 props 属性类型,例子:

class MyComponent extends React.Component {
  render() {
    return (
      <ul>
        <li>{this.props.name}</li>
        <li>{this.props.age}</li>
      </ul>
    );
  }
}

// 校验类型
MyComponent.propTypes = {
  name: PropTypes.string, // 这里的 PropTypes 变量是全局挂载的
  age: PropTypes.number,
};

ReactDOM.render(
  <MyComponent name="Bob" age={18} />,
  document.getElementById("test")
);

例子中是在组件对象上以 . 形式直接添加属性 propTypes 的。要想校验 props 属性类型,需要在组件类上设置 propTypes 属性(注意首字母小写),而验证器则是 PropTypes 提供。可参考文档:使用 PropTypes 进行类型检查

你也可以在把 propTypes 属性作为组件类的静态属性写入:

<script type="text/babel">
  class MyComponent extends React.Component {
    render() {
      return (
        <ul>
          <li>{this.props.name}</li>
          <li>{this.props.age}</li>
        </ul>
      );
    }
		
    // propTypes 作为类的静态属性
    static propTypes = {
      name: PropTypes.string,
      age: PropTypes.number,
    };
  }

  ReactDOM.render(
  <MyComponent name="Bob" age={18} />,
  document.getElementById("test") 
  );
</script>

函数组件使用 props

函数组件无法使用 state,但是可以使用 props,因为 props 可以通过参数传入函数中。例子:

<script type="text/babel">
  // 函数组件
  function MyComponent(props) {
    return (
      <ul>
        <li>{props.name}</li>
        <li>{props.age}</li>
      </ul>
    );
  }
  // 校验类型
  MyComponent.propTypes = {
    name: PropTypes.string,
      age: PropTypes.number,
  };

  ReactDOM.render(
    <MyComponent name="Bob" age={18} />,
    document.getElementById("test")
  );
</script>

批量传递 props

props 可以批量传递:

<script type="text/babel">
  class MyComponent extends React.Component {
    render() {
      return (
        <ul>
          <li>{this.props.name}</li>
          <li>{this.props.age}</li>
        </ul>
      );
    }
  }

  const data = { name: "Bob", age: 18 };
	
  // 使用 {...data} 的方式可以批量传递属性
  ReactDOM.render(
  <MyComponent {...data} />,
  document.getElementById("test")
  );
</script>

Refs

React 中的 Refs 可以让我们访问 DOM 节点,它有三种使用方式:

旧的方式,String 类型的 Refs

此种方式已不被推荐使用,但是还能用,在以后的版本中可能会移除,使用比较简单:

<script type="text/babel">
  class MyComponent extends React.Component {

    handleAlert = () => {
      // 在 refs 中获取定义的 ref 标识
      const { myInput } = this.refs;
      console.log(myInput); // <input type="text">
      alert(myInput.value);
    };

    render() {
      return (
        <div>
          {/* 使用 ref="" 方式直接定义字符串标识  */}
          <input ref="myInput" type="text" />
          <button onClick={this.handleAlert}>alert</button>
        </div>
      );
    }
  }

  ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>

回调 Refs

此种方式定义的 ref 不再是字符串,而是一个回调函数,函数的参数就是当前标识的 DOM 节点或者组件实例。

<script type="text/babel">
  class MyComponent extends React.Component {
    
    handleAlert = () => {
      // 直接从组件实例上获取 myInput
      console.log(this.myInput); // <input type="text">
      alert(this.myInput.value);
    };

    render() {
      return (
        <div>
          {/* ref 直接定义成一个回调函数,参数就是节点本身,将它赋值给组件的一个 myInput 属性 */}
          <input ref={(ele) => (this.myInput = ele)} type="text" />
          <button onClick={this.handleAlert}>alert</button>
        </div>
      );
    }
  }

  ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>

如上代码演示了使用回调 Refs 的方法,但是这种方式有一个需要注意的地方,React 官网给出以下提示:

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

意思是说,像上面这种定义内联函数的方式,在组件更新时,即除第一次外,后面 render 函数再执行时,这个定义的 ref 内联函数会被调用两次,其参数第一次是 null,第二次是 DOM 元素。之所以出现这样的情况,是因为组件更新时,会清空一下之前的 ref,保证更新。

其实这种情况是无关紧要的,我们可以照常使用内联函数的方式定义 ref,如果想避免这种情况,可以把内联函数移出来写:

<script type="text/babel">
  class MyComponent extends React.Component {
    // 内联函数移出来写
    setInputRef = (ele) => {
      this.myInput = ele;
      console.log(ele);
    };

    handleAlert = () => {
      console.log(this.myInput);
      alert(this.myInput.value);
    };

    render() {
      return (
        <div>
          {/* 这里不再直接定义内联函数 */}
          <input ref={this.setInputRef} type="text" />
          <button onClick={this.handleAlert}>alert</button>
        </div>
      );
    }
  }

  ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>

新的,被推荐的创建 Refs 方法

现在 React 推荐使用 React.createRef(),然后通过 ref 属性附加到 React 元素上。

<script type="text/babel">
  class MyComponent extends React.Component {
    // 创建 ref
    myInput = React.createRef();

    handleAlert = () => {
      console.log(this.myInput.current); // 这里需要注意,元素是在 current 属性上
      alert(this.myInput.current.value);
    };

    render() {
      return (
        <div>
          {/* 将创建好的 ref 附加到元素上 */}
          <input ref={this.myInput} type="text" />
          <button onClick={this.handleAlert}>alert</button>
        </div>
      );
    }
  }

  ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>

上面就是使用 React.createRef() 方法创建 ref 的方式,特别需要注意的是,创建出来的 ref 的值是一个对象,我们需要的 DOM 元素是放在对象的 current 属性上,如上面的 this.myInput.current

结尾

如有错误,欢迎指出。