iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 29
1
Modern Web

I Want To Know React系列 第 29

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 模式下檢查 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 也會報錯提醒。

參考資料


上一篇
I Want To Know React - Context 範例 & 使用技巧
下一篇
I Want To Know React - 中場休息
系列文
I Want To Know React30

尚未有邦友留言

立即登入留言