React prop类型检查与Dom

全文共 1870 个字

使用PropTypes进行类型检查

当应用不断增长时,可以用过类型检查发现很多bug。对于某些应用,可以使用JavaScript扩展工具来完成,比如使用  Flow TypeScript 来检查整个工程。除了引入外部工具之外,React也提供了参数类型检查的功能,只需要为每一个属性指定一个 propTypes 即可:

// 15.5之后,需要单独引入依赖才能使用类型检查
import PropTypes from 'prop-types';
//定义组件
class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

//指定类型检查
Greeting.propTypes = {
  name: React.PropTypes.string
};

PropTypes将会设定一系列验证器,这些验证器用于确保组件接受到的参数(props)是指定的类型。比如上面的例子,当一个错误的类型被组件接收到,会有一段警告内容使通过console输出。propsTypes仅仅在开发模式下使用

PropTypes

以下是各种验证器的示例:

MyComponent.propTypes = {
  // 指明每个传入参数的具体类型,传递的参数仅限于这些JavaScript的内置类型
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // number、string、element或者一个列表都是允许的
  optionalNode: PropTypes.node,

  // 接收一个React组件
  optionalElement: PropTypes.element,

  // 声明这个参数只接收某个对象(class)的实例,适用于传递一个对象作为配置参数的
  optionalMessage: PropTypes.instanceOf(Message),

  // 指定参数限定在多个对象之内
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 指定参数允许多个类型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 指定类型的列表
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 指定传递某个类型,是一个对象不是数据本身
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 指定传递参数的结构,适用于传递一个对象时限定对象的结构
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 表明这个参数是必须要传递的参数,在使用这个组件时,这个参数必须传入数据
  requiredFunc: PropTypes.func.isRequired,

  // 允许任何类型的数据。
  requiredAny: PropTypes.any.isRequired,

  // 指定一个自定义的检查器,当检查失败时需要返回一个Error对象来指明错误。
  // 错误只需要返回,切记不能使用throw或console.warn输出
  // 不适用于 oneOfType 类型。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 用于检测一个数组传递的自定义检查器,适用于arrayOf和objectOf类型。
  // 当出现检查错误时需要返回Error
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

限定至少接收一个子元素

可以使用 PropTypes.element 来指明组件必须接收一个子元素:

class MyComponent extends React.Component {
  render() {
    // This must be exactly one element or it will warn.
    const children = this.props.children;
    return (
      <div>
        {children}
      </div>
    );
  }
}

MyComponent.propTypes = {
  children: React.PropTypes.element.isRequired
};

设定props默认值

还可以使用 defaultProps来指定默认值:

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

// 指定props.name的默认值:
Greeting.defaultProps = {
  name: 'Stranger'
};

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

Refs和真实Dom

在典型的React数据流中,props参数传递的唯一接口。当需要修改参数时,必须修改props值并重新渲染(render)。然而,有很多场景需要在单向数据流之外修改子组件,React提供“Refs”特性来直接修改真实Dom元素。

什么时候需要使用Refs

当遇到以下情况时,建议使用Refs特性:

  • 需要管理聚(focus)、文档选择或媒体回放等真实Dom事件时。
  • 触发需要马上执行的动画。
  • 引入第三方库时。

避免将Refs用于任何声明性的工作,如使用一个props.isOpen参数来代替Dialog的open()和close()接口。

将Ref添加到Dom元素中

React支持在任何组件上使用ref。ref属性提供一个回调方法,当组件被渲染或被移除后,这个回调方法会被调用。

当ref属性用于一个HTML元素时,ref的回调方法会获取Dom的实例。例如,下面的例子获取到input标签的Dom实例并保存到this.textInput变量中,这个变量一直指向Dom节点。

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.focus = this.focus.bind(this);
  }

  // 定义一个focus方法
  focus() {
    this.textInput.focus();
  }

  render() {
    return (
      <div>
        <input
          type="text"
          ref={(input) => { this.textInput = input; }} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focus}
        />
      </div>
    );
  }
}

当Dom元素被渲染后,React会回调ref指定的方法,并传递当前Dom的实例作为参数,当Dom被移除时,ref指向的方法也会被调用,传入的参数为null。

使用ref回调方法来设置class的属性是获取真实Dom对象的常用方法,上面的例子给出了一个编写方式,只要语法正确你可以用各种方式来编写,如更简短的: ref={input => this.textInput = input}

给class组件增加一个Ref属性

当ref用于一个由class关键字声明的自定义组件时,ref指向的回调方法会在组件完成渲染后被回调,传递的参数是组件的实例。例如下面的例子,在 CustomTextInput 组件完成渲染后立即模拟一次focus事件:

class AutoFocusTextInput extends React.Component {
  componentDidMount() {//完成渲染后被回调
    this.textInput.focus();//聚焦到当前组件
  }

  render() {
    // CustomTextInput 已经在上一段代码中声明
    return (
      <CustomTextInput
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

必须用class来定义 CustomTextInput 组件才会生效。

给Function声明的组件设定Refs

不能再function定义的组件直接使用ref,因为在声明时他并没有实例化:

function MyFunctionalComponent() {
  return <input />;
}

class Parent extends React.Component {
  render() {
    // 错误,这里的ref不会有任何效果。
    return (
      <MyFunctionalComponent
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

最合理的方式是将function定义的组件转换为class,这和我们需要使用state来控制状态是一个道理。不过在function组件中,如果内部引用的是另一个class组件也是可以使用Refs特性的:

function CustomTextInput(props) {
  // 在这里声明textInput,每次重新渲染时,都会新生成一个本地变量
  let textInput = null;

  // 每次重新渲染时,都会新生成一个回调方法
  function handleClick() {
    textInput.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={(input) => { textInput = input; }} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}

切勿过度使用Refs特性

可能在了解Refs的机制后,某些开发人员更倾向于在代码中使用Refs这种“操作即发生”特性来实现功能。但是在使用之前最好多花点时间来思考为什么状态需要由不同的组件层次来控制,通常情况下组件之间的状态最好由他们共同的祖先来控制: React 状态、事件与动态渲染

*使用警告

如果ref的回调方法被定义为一个内联方法,它在更新之前会发生2次调用,第一调用时会传递一个null值,第二次会赋予真正的Dom对象。这是因为在每次渲染时都会有一个新的方法实例被创建所以React必须清除已有的ref并创建一个的ref。可以通过将ref回调方法定义为类的绑定方法来避免这种情况,但请注意,在大多数情况下,这并不会导致什么问题。