I Want To Know React - PropTypes & DefaultProps

此文件纪录 React PropTypes 的使用方式与语法

相信读者在使用纯 JavaScript 的时候一定有遇过搞不清楚 function 参数的型别的状况。在使用 React component 时也是,如果没有好好定义 component props 型别的话,每次使用 component 都会要去查程序码才能知道每个 props 如何使用,这将会造成开发上的困难。

所幸 React 内建就有提供方法检查 component 每个 props 的型别。此内建的检查方法就是本篇文章要介绍的主题:PropTypes 与 DefaultProps。

React - PropTypes

除了使用 FlowTypeScript 等 JavaScript 静态语法检查器来检查以外,React 在每个 component 中也提供了 propTypes 属性来检查 props 的型别。

一个简单的范例如下:

function Profile(props) {
	return <div>{props.name}, {props.age}</div>
}

Profile.propTypes = {
	name: PropTypes.string,
	age: PropTypes.number,
}

功能

更详细一点讲,PropTypes 的功能是以下两个:

  • 定义 component prop 的型别

    定义 component prop 的好处显而易见,他可以帮助我们更容易了解 component 的使用方式,不再需要进到 component 内的程序码即能知道 component 该如何使用。从某种角度来讲,他可以算是 component 的使用文件。

  • 在 React development 模式下,runtime 去检查 component 的 prop 型别是否正确

    如果在 development 模式下使用错误的 prop 型别,则 console 会发出警告。

    以上面的 Profile 为范例范例,如果将 number 赋值给 Profilename prop,则 console 中会出现以下的警告:

    <Profile name={123} />
    // Warning: Failed prop type: Invalid prop `name` of type `number` supplied to `Profile`, expected `string`. in Profile
    

    检查 component prop 可以帮助我们更快速发现错误,不会因为错误而逐一检查错误出在哪里,这可以有效地加快开发速度;PropTypes 也可以帮助我们提早发现错误,可减少 bug 被埋藏起来的问题。

    然而检查型别毕竟是消耗效能的行为,所以 React 只会在 development 模式下检查 PropTypes 而已,在 production 模式下则不会检查。

接下来来看看如何使用 PropTypes。

使用 PropsTypes

安装

React 在 15.5 版之後就将 PropTypes 从 React Core 函式库中搬移到 prop-types 这个 node module 上了,因此如果要使用 PropTypes,需要先进行安装:

npm install --save prop-types

语法简介

PropTypes 的语法很简单。任何 component(不论是 class component、function component 还是 React.memo 跟 React.forwardRef 创建出来的 component)都可以加上 propTypes 这个属性来定义 component 的 props 型别:

import PropTypes from 'prop-types';

function MyComponent(props) { /* ... */ }

MyComponent.propTypes = {
	propName1: propTypes.string,
  propName2: propTypes.number,
  // ...
}

Component.propTypes 属性的型别是 JavaScript object,此 object:

  • 每个 key 代表 component 定义的 prop 的名称。
  • 每个 key 的 value 则定义 component prop 的型别,通常会是使用 prop-types node module(後面将简称为 PropTypes)提供的函式来定义型别,如 PropTypes.stringPropTypes.number ...etc。

PropTypes 物件(prop-types node module)

prop-types 这个 node module 会 export 一个 PropTypes 物件。

PropTypes 物件中提供了各式各样的属性可以用来检查 props 的型别,例如:

PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.oneOf([])
// ...etc

这些 PropTypes 的每个属性其实本质上都是一个 validator function,可作为型别检查器用来检查 component props 的型别。

而每个 PropTypes 属性还可以再使用 isRequired 的属性,用来定义使用 component 时是否必须提供某个 prop:

PropTypes.array.isRequired
PropTypes.bool.isRequired
PropTypes.func.isRequired
PropTypes.number.isRequired
PropTypes.object.isRequired
PropTypes.string.isRequired
PropTypes.node.isRequired
PropTypes.oneOf([]).isRequired
// ...etc

同样的,PropTypes.xxx.isRequired本质也是 validator function,只是多做了 prop 是否存在的检查而已。

同样的道理,因为这些检查器实际上都只是 function 而已,所以开发者是能够客制化自己的 validator function 来做 props 的型别检查的。

接着我们将学习每个 PropTypes 属性的功能与用法。

PropTypes JavaScript 基本型别检查

使用以下的 PropTypes 属性可以检查 props 是否符合对应的 JavaScript 基本型别:

MyComponent.propTypes = {
  optionalNumber: PropTypes.number,
  optionalString: PropTypes.string,
  optionalBool: PropTypes.bool,
  optionalObject: PropTypes.object,
  optionalSymbol: PropTypes.symbol,
  optionalArray: PropTypes.array,
  optionalFunc: PropTypes.func,
}

合法的 component props 范例如下:

<MyComponent
  optionalNumber={123}
  optionalString="123"
  optionalBool={false}
  optionalObject={{ test: 123 }}
  optionalSymbol={Symbol()}
  optionalArray={[1, 2, 3]}
  optionalFunc={() => {}}
/>

PropTypes.node

PropTypes.node 可以用来检查 prop 是否是可以被 React render 的型别:

MyComponent.propTypes = {
  optionalNode: PropTypes.node,
}

可以被 React render 的型别包括:

  • React elements
  • Fragment
  • numbers
  • strings
  • array

合法的 component props 范例如下:

<MyComponent
  optionalNode={<div>123</div>}
  // can also be:
  // optionalNode={<Fragment></Fragment>}
  // optionalNode={123}
  // optionalNode="123"
  // optionalNode={[1, 2, 3]}
/>

PropTypes.element

PropTypes.element 可以用来检查 prop 是否为 React element:

  • ?小提醒:Fragment 也是一种 React element
MyComponent.propTypes = {
  optionalElement: PropTypes.element,
}

合法的 component props 范例如下:

<MyComponent
  optionalElement={<div>123</div>}
  // can also be:
  // optionalElement={<Fragment></Fragment>}
/>

PropTypes.elementType

PropTypes.elementType 可以用来检查 prop 是否为合法 React element type:

MyComponent.propTypes = {
  optionalElementType: PropTypes.element,
}

React element type 包括:

  • 自定义的 component type(可能是 function 也可能是 class)
  • 原生的 element type(e.g. "div""span" ...etc,会是用 string 表示)

合法的 component props 范例如下:

class MyOtherComponent extends React.Component {
	render() {/* ... */}
}

<MyComponent
  optionalElementType={MyOtherComponent}
  // can also be:
  // optionalElementType="div"
  // optionalElementType="span"
/>

PropTypes.instanceOf()

PropTypes.instanceOf() 可以用来检查 prop 是否为某个 class 的 instance。

因此 instanceOf() 需要带入:

  • expectedClass: 一个 class 型别

范例如下:

class MyClass {/* ... */}

MyComponent.propTypes = {
  optionalInstanceOf: PropTypes.instanceOf(MyClass),
}

合法的 component props 范例如下:

const myClass = new MyClass();

<MyComponent
  optionalInstanceOf={myClass}
/>

PropTypes.oneOf()

PropTypes.oneOf() 可以用来检查 prop 是否为指定的值之一。

因此 oneOf() 需要带入:

  • expectedValues:包含一组指定值的 array

范例如下:

MyComponent.propTypes = {
  optionalEnum: PropTypes.oneOf([123, 'Henry']),
}

需要注意的是,检查是否为指定值的方法是透过 Object.is(类似 ===) 检查,因此不适合用来检查 prop 是否为特定内容的 object。如果有这个需求的话,应使用 PropTypes.shapePropTypes.exact

合法的 component props 范例如下:

<MyComponent
  optionalEnum={123}
	// can also be:
  // optionalEnum="Henry"
/>

PropTypes.oneOfType()

PropTypes.oneOfType() 可以用来检查 prop 是否为指定的型别之一。

因此 oneOf() 需要带入:

  • expectedTypes:包含一组指定型别的 array

范例如下:

class MyClass {/* ... */}

MyComponent.propTypes = {
  optionalUnion: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.string,
    PropTypes.instanceOf(Message)
  ]),
}

合法的 component props 范例如下:

const myClass = new MyClass();

<MyComponent
  optionalUnion={123}
	// can also be:
  // optionalUnion="Henry"
  // optionalUnion={myClass}
/>

PropTypes.arrayOf()

PropTypes.arrayOf() 可以用来检查 prop 是否为指定型别组成的 array。

因此 arrayOf() 需要带入:

  • expectedTypeChecker:指定的 validator function(型别检查器)

范例如下:

MyComponent.propTypes = {
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
  optionalMixedArrayOf: PropTypes.arrayOf(
    PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  ),
}

因为是带入的参数是 validator function,因此除了常见的基本型别检查器( PropTypes.numberPropTypes.string ...etc)以外,也可以发挥创意带入 PropTypes.oneOfPropTypes.oneOfType() 等较复杂的检查器。

合法的 component props 范例如下:

<MyComponent
  optionalArrayOf={[1, 3, 123, -1]}
  optionalMixedArrayOf={[1, 'Henry', 123]}
/>

PropTypes.objectOf()

PropTypes.objectOf() 可以用来检查 prop 是否为指定型别组成的 object。

因此 objectOf() 需要带入:

  • expectedTypeChecker:指定的 validator function(型别检查器)

范例如下:

MyComponent.propTypes = {
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),
  optionalMixedObjectOf: PropTypes.objectOf(
    PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  ),
}

PropTypes.arrayOf 相同,因为是带入的参数是 validator function,因此除了常见的基本型别检查器以外,也可以发挥创意带入 PropTypes.oneOfPropTypes.oneOfType() 等较复杂的检查器。

合法的 component props 范例如下:

<MyComponent
  optionalObjectOf={{ a: 1, b: 123, c: 345 }}
  optionalMixedObjectOf={{ a: 1, b: 'Henry', c: 123 }}
/>

PropTypes.shape()

PropTypes.shape() 可以用来检查 prop 是否为指定 key 组成的 object。

因此 shape() 需要带入:

  • shapeTypes:一个 object,包含指定的 key 与对应的 validator function(型别检查器)作为 value
MyComponent.propTypes = {
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number,
    type: PropTypes.oneOf(['primary', 'secondary', 'warning'])
  }),
}

同样的,因为 shapeTypes 中每个 key 的 value 都会是 validator function,因此除了基本型别检查器以外也可以带入 PropTypes.oneOfPropTypes.oneOfType() 等较复杂的检查器。

合法的 component props 范例如下:

<MyComponent
  optionalObjectWithShape={{ color: '#fff', fontSize: 400, type: 'primary' }}
/>

PropTypes.exact()

PropTypes.exact() 可以用来检查 prop 是否为指定 key 组成的 object,且此 object 不可以有额外的 key。如果有额外的 key,React 就会显示警告。

因此 exact() 需要带入:

  • exactShapeTypes:一个 object,包含指定的 key 与对应的 validator function(型别检查器)作为 value
MyComponent.propTypes = {
  optionalObjectWithStrictShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number,
    type: PropTypes.oneOf(['primary', 'secondary', 'warning'])
  }),
}

合法的 component props 范例如下:

<MyComponent
  optionalObjectWithStrictShape={{ color: '#fff', fontSize: 400, type: 'primary' }}
/>

PropTypes.XXX.isRequired

上述的 PropTypes 属性後面都可搭配 isRequired 使用,代表使用 component 时必须提供此 prop。

如果没有 isRequired 的话,则 prop 会是可提供,也可不提供的(Optional):

MyComponent.propTypes = {
  optionalNumber: PropTypes.number,
  requiredObjectWithStrictShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number,
    type: PropTypes.oneOf(['primary', 'secondary', 'warning']).isRequired
  }).isRequied,
}

合法的 component props 范例如下:

<MyComponent
  // optionalNumber is an optional prop, so it's ok to not provide optionalNumber.
  // ---
  // requiredObjectWithStrictShape prop must be provide.
  // but only 'type' key is required, other key are optional, so it's ok to not provide them.
  requiredObjectWithStrictShape={{ type: 'primary' }}
/>

可以看到, optionalNumber 是可有可无的,因此不提供也是合法的。

requiredObjectWithStrictShape 则是必要的,但是此 object 的内容只有 type 是必须要提供的,其他不提供也算合法。

PropTypes.any

PropTypes.any 代表一个 prop 可以是任意的内容。

通常与 isRequired 一起使用,代表此 prop 必须存在,但可以是任意型别:

MyComponent.propTypes = {
  any: PropTypes.any
  requiredAny: PropTypes.any.isRequired
}

合法的 component props 范例如下:

<MyComponent
  any: [1, 'test', <div />, true]
  requiredAny: { a: 'test' }
/>

客制化检查器

PropTypes 物件段落所讲,上述的 PropTypes 属性皆为 function,因此只要有了解这些 validator function 的 input 与 output,开发者也可客制化自己的型别检查器:

客制化的 validator function 可以带入以下三个参数:

  • props:代表目前带入的所有 props
  • propName:代表目前正在检查的 prop 名字
  • componentName:代表 component 的名字

并在 prop 不符合格式时需要回传:

  • errorError 的 instance,代表格式错误,并可添加错误讯息

此外,arrayOf()objectOf() 等较复杂的检查器内也可带入客制化的检查器,如下所示:


MyComponent.propTypes = {
	// You can also specify a custom validator. It should return an Error
	// object if the validation fails. Don't `console.warn` or throw, as this
	// won't work inside `oneOfType`.
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },
  // You can also supply a custom validator to `arrayOf` and `objectOf`.
  // It should return an Error object if the validation fails. The validator
  // will be called for each key in the array or object. The first two
  // arguments of the validator are the array or object itself, and the
  // current item's key.
  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.'
      );
    }
  })
}

合法的 component props 范例如下:

<MyComponent
  customProp: 'matchme'
  customArrayProp: ['matchme', 'matchme123']
/>

检查 children prop

Component 的 children 内容(props.children)也是可以被检查的,如下所示:

MyComponent.propTypes = {
  children: PropTypes.number
}

合法的 component props 范例如下:

<MyComponent>{123}</MyComponent>

不应该检查 key prop

如 所提,key 是 React 内部所使用的 prop,并不会传给 component 使用,所以应该不应该客制化 key 的内容。就算为 key 定义 propType,component 拿到的 key 内容也永远会是 undefined

如果在 PropTypes 中定义 key 时,React 就会报错:

MyComponent.propTypes = {
  key: PropTypes.number
}
// Warning: Profile: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop

React - defaultProps

除了检查 props 的型别以外,React 在每个 component 中也提供了 defaultProps 属性来为 props 添加预设值。

如果上层 component 没有传对应的 props 下来,则 React 就会为 component 带入 defaultProps 设定的值。

让 component 有预设的行为时常可以避免一些基本且冗长的 prop 设定。一个简单的范例如下:

function Greetings(props) {
	return <div>Hello, {props.name}</div>
}

Greetings.propTypes = {
	name: PropTypes.string,
}

Greetings.defaultProps = {
	name: 'Guest'
}

语法

DefaultProps 的语法很简单。任何 component 都可以加上 defaultProps 这个属性来设定 component prop 的预设值:

function MyComponent(props) { /* ... */ }

MyComponent.defaultProps = {
	propName1: 'someValue1',
  propName2: 'someValue2',
  // ...
}

Component.propTypes 属性的型别是 JavaScript object,此 object:

  • 每个 key 代表 component 定义的 prop 的名称。
  • 每个 key 的 value 则用来设定 component prop 的预设值。

如果有使用 Babel 的 transform-class-properties 则也可以在 class component 中使用 static 语法定义 defaultProps

class Greeting extends React.Component {
  static defaultProps = {
    name: 'stranger'
  }

  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

PropTypes 也会检查 defaultProps

一个需要注意的点是,React 使用 component.propTypescomponent.defaultProps 的顺序如下:

  1. 先为 component props 补上 defaultProps 设定的预设值。
  2. propTypes 检查 props 的型别。

因此就算 defaultProps 型别设定错误,React 的 propTypes 依然可以贴心的为开发者检查到问题,如下所示:

function Greetings(props) {
	return <div>Hello, {props.name}</div>
}

Greetings.propTypes = {
	name: PropTypes.string,
}

Greetings.defaultProps = {
	name: 123
}
// Warning: Failed prop type: Invalid prop `name` of type `number` supplied to `Greetings`, expected `string`. in Greetings

小结

在这个章节中,我们介绍的 React 的 PropTypes 与 DefaultProps。

PropTypes 是用来在 development 模式下检查 component prop 的型别。通常会与 prop-types 这个 node module 一起使用,此 node module 提供了一组 validator function 用来检查型别,
而开发者也可以实作自己的检查器 function 来检查 props 内容。

DefaultValue 则可以为 component 设定预设值,当 component 没有带入 prop 时,React 就会为 component 带入 defaultProps 设定的值。

最後在执行的顺序上 defaultProps 会先被设定後,React 才会进行 propTypes 的检查,因此 defaultProps 错误的时候 React 也会报错提醒。

参考资料


<<:  Are You Ready? ES2022!

>>:  Nice day 29 (iphone10s 功能挖掘)-好用捷径介绍

半导体布局设计工程师能力监定上课

请问各位大大,小的想去考111年的半导体布局设计工程师能力监定,请问哪里可以有实体或线上课程呢,上完...

Day19 - Ruby 杂凑处理入门

GitHub 网址:https://github.com/ Heroku 网址:https://w...

第04天 - 一些些的HTML

1.首先粗浅的讲一下 HTML 的语法,如下: <!-- 以下这个半形的中括号,叫做标签 ; ...

【Day 08】工厂方法设计模式(Python)

前言 上一篇我们讨论DDD的战术设计,它建议引用各种设计模式,提高生产力,因此接下来,就来介绍各种设...

Vue.js 从零开始:v-model

表单类型是网页很常见的呈现方式,表单元素有文字框<input>、<textarea...