🔰

React入門 ~PropTypes編~

8 min read

React 入門記事、第3弾。
今回はコンポーネントに渡す props の使用を補助するものである PropTypes について書きました。

この記事は、過去に Qiita および個人ブログへ投稿した記事の転載です。

※2021/08/26追記 全体的に少し内容を見直しました。

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

ログインするとコメントできます