🔰

JS×Reactにおけるpropsの型定義を担うPropTypes入門

2020/09/23に公開

今回はコンポーネントに渡す props の使用を補助するものである PropTypes について書きました。

※2021/08/26追記 全体的に少し内容を見直しました。
※2022/01/24追記 あくまでライブラリの記事であるとわかりやすくするために、記事タイトルを変更しました(旧:React入門 ~PropTypes編~)

PropTypes とは?

公式:
https://ja.reactjs.org/docs/typechecking-with-proptypes.html
https://github.com/facebook/prop-types

React で登場するコンポーネントは、 props という任意の値を受け取ることができるようになっています。
受け取ったコンポーネント側でこの props の値を使い、レンダリング内容に変化をつけたり、ロジックを作ったり。

一見、便利な props ではありますが、通常ではどんな値でも受け取ることができてしまいます。
そのため、props の型補完が効かなかったり。
誤って想定と違う値を渡してしまうと、予期しない動作をしてアプリがクラッシュする...いったことも起こる可能性があったり。

TypeScript を使用すれば props の型定義ができるため、こういった問題に対応できるのですが...。
JavaScript を使用している場合は、このことを常に考慮する必要があります。

これを補助するものとして、型定義機能を提供してくれるのがPropTypesです。
元々は React 本体に組み込まれていましたが、バージョン15.5よりprop-typesという別パッケージとして切り分けされました。

インストール

yarn add prop-types

今回の使用バージョンは以下のとおりです。

  • React:17.0.2
  • prop-types:15.7.2

基本的な使い方

App.js(コンポーネント呼び出し側)
import PropsTypesComponent from './PropTypesComponent';

const App = () => {
  return (
    <PropsTypesComponent name="太郎" />
  )
}

export default App;
PropsTypesComponent.js(コンポーネント側)
import PropTypes from 'prop-types';

const PropTypesComponent = ({ name }) => {
  return (
    <h2>Hello {name}</h2>
  );
}

PropTypesComponent.propTypes = {
  name: PropTypes.string
};

export default PropTypesComponent;

ライブラリを import

import PropTypes from 'prop-types';

props ごとの型定義を記述

コンポーネント.propTypes = {
  props名: PropTypes.型定義
}

今回の場合

PropTypesComponent.propTypes = {
  name: PropTypes.string
};

これで props の型定義ができました。
このコンポーネント呼び出し時に、props の入力補完が効くようになります。

また、自動でバリデーションチェックが行われるようになり、無効な値が渡された場合は DevTools のコンソールに Warning が出力されます。
上記の例では問題ありませんが、例えば PropsTypesComponent に渡している name の値を「1」など、string 以外の値にしてみます。
すると、以下のような Warning がコンソールに出力されているはずです。

warning.png

あくまでコンソール上に Warning を出力するだけではありますが、これが出ないようにコーディングすれば、予期せぬ動作を減らすことができるわけです。
加えて props にどんな型の値が入るのか可視化される。というメリットもあります。

ただ、このバリデーションチェックはパフォーマンス上の理由から開発モードの場合のみ動作することに注意です。

型定義の種類

数値

PropTypes.number

受け付ける例

1
1.0

文字列

PropTypes.string

受け付ける例

'太郎'
'1'

真偽値

PropTypes.bool

受け付ける例

true
false

一応補足として、あくまで真偽値なので'true'などはダメです。
文字列扱いなので、バリデーションに引っ掛かります。

配列

PropTypes.array

受け付ける例

[1, 'A']
[{ id: 'A'}, { id: 'B' }]

配列であれば、その中の値の型までは問わないため非推奨のようです。
中の値の型までチェックする場合はarrayOfを使います。

PropTypes.arrayOf(型定義の種類)

// 例
PropTypes.arrayOf(PropTypes.number)

受け付ける例
※ PropTypes.arrayOf(PropTypes.number) の場合

[1, 2, 3]

オブジェクト

PropTypes.object

受け付ける例

{ a: 'A', b: 'B' }

オブジェクトであれば、その中の値の型までは問わないため非推奨のようです。
中の値の型までチェックする場合はobjectOfshape、もしくはexactを使います。

objectOfは特定の型のみの場合。

PropTypes.objectOf(型定義の種類)

// 例
PropTypes.objectOf(PropTypes.number)

受け付ける例
※ PropTypes.objectOf(PropTypes.number) の場合

{ a: 1, b: 2 }

shapeは型がバラバラの場合。

PropTypes.shape({
  props名: 型定義の種類,
  props名: 型定義の種類
})

// 例
PropTypes.shape({
  num: PropTypes.number,
  str: PropTypes.string
})

受け付ける例
※上の設定例の場合

{ num: 1, str: '太郎' }

exactも型がバラバラの場合です。
shapeとの違いは、型定義した以外のものがオブジェクトに追加されているとバリデーションに引っ掛かります。
こちらの方がより厳密に props をチェックする感じです。

PropTypes.exact({
  props名: 型定義の種類,
  props名: 型定義の種類
})

// 例
PropTypes.exact({
  num: PropTypes.number,
  str: PropTypes.string
})

受け付ける例
※上の設定例の場合

// ここにこの二つ以外のキー、値があると警告
{ num: 1, str: '太郎' }

関数

PropTypes.func

受け付ける例

() => {
  console.log('func');
}

シンボル

PropTypes.symbol

受け付ける例

Symbol()
Symbol('test')

恥ずかしながら自分はシンボルって何?状態だったので調べたのですが、ES6 で追加された新しいプリミティブのデータ型だったのですね。

でも、いまいち使い方がよくわからない...。

特定の値のいずれかの場合

PropTypes.oneOf(['値1', '値2'])

// 例
PropTypes.oneOf(['A', 'B', 'C'])

受け付ける例
※上の設定例の場合

'A'
'B'
'C'

いろんなデータ型が渡される可能性がある場合

PropTypes.oneOfType([
  型定義の種類,
  型定義の種類
])

// 例
PropTypes.oneOfType([
  PropTypes.number,
  PropTypes.string
])

受け付ける例
※上の設定例の場合

1
'1'
'A'

クラスオブジェクト

PropTypes.instanceOf(クラス名)

// 例
PropTypes.instanceOf(Date)

受け付ける例
※上の設定例の場合

new Date()

React Element

PropTypes.element

受け付ける例

<Test /> // 独自定義のコンポーネント
<p>Test</p>

React Element Type

PropTypes.elementType

React.element と似ていますが、こちらは以下のような記述がありました。

関数、文字列、または「要素のような」オブジェクト(React.Fragment、Suspenseなど)

詳細は React の isValidElementType を見てね、とのこと。
https://github.com/facebook/react/blob/main/packages/shared/isValidElementType.js

受け付ける例

Test // 独自定義のコンポーネント名(JSX を返す関数)
React.Fragment
React.Context
React.Suspense

レンダリングできるもの

PropTypes.node

受け付ける例

1
'A'
['a', 'b']
<p>Test</p>
<Test /> // 独自定義のコンポーネント

数値、文字列、配列、React Element であればいいようです。
真偽値やオブジェクトはバリデーションに引っ掛かりました。

型は問わない

PropTypes.any

どんな型でも受け付けるよ。というやつです。
これを使ってしまうと PropTypes の意味があまりなくなってしまうので、どうしても使いたい時だけに留めておきましょう。

必須

PropTypes.型定義.isRequired

// 例
PropTypes.number.isRequired
PropTypes.any.isRequired

カスタムルール

// カスタムルールの定義
const customProp = (props, propName, componentName) => {
  if (!/test/.test(props[propName])) {
    return new Error(
      'Invalid prop `' + propName + '` supplied to' +
      ' `' + componentName + '`. Validation failed.'
    );
  }
}

// カスタムルールの使用
コンポーネント名.propTypes = {
  props名: customProp
}

この例は props の値にtestという文字列が含まれているかチェックするものです(公式ドキュメントのコードをベースにしています)

props のデフォルト値

defaultPropsを使って、props のデフォルト値を設定できます。
もしその props に値が渡されなかった場合は、このデフォルト値がセットされます。
また、型定義と並行して使用している場合は、このデフォルト値セットが行われた後でバリデーションチェックが行われます。

コンポーネント名.defaultProps = {
  props名:}

// 例
PropTypesComponent.defaultProps = {
  defaultValue: 'default'
}

2021/08/26 時点で、自分は TypeScript をメインで使うようになったので、PropTypes と少し縁遠くなりました。
ただ、JavaScript のままのアプリも一部管理しているので、そちらでは今でも継続して使用しています。

最初は、渡す props が多いコンポーネントだと型定義がめんどくさいと感じる人少なくないんじゃないかなと。
その分、それ以上のメリットがあると思っていますし、JavaScript × React アプリ開発時の使用をお勧めします。
(自分の場合は慣れてくると、逆に型定義がないと不安な気持ちなるようになりました(笑))

参考リンク

株式会社ゆめみ

Discussion