JS×Reactにおけるpropsの型定義を担うPropTypes入門
今回はコンポーネントに渡す props の使用を補助するものである PropTypes について書きました。
※2021/08/26追記 全体的に少し内容を見直しました。
※2022/01/24追記 あくまでライブラリの記事であるとわかりやすくするために、記事タイトルを変更しました(旧:React入門 ~PropTypes編~)
PropTypes とは?
公式:
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
基本的な使い方
import PropsTypesComponent from './PropTypesComponent';
const App = () => {
return (
<PropsTypesComponent name="太郎" />
)
}
export default App;
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 を出力するだけではありますが、これが出ないようにコーディングすれば、予期せぬ動作を減らすことができるわけです。
加えて 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' }
オブジェクトであれば、その中の値の型までは問わないため非推奨のようです。
中の値の型までチェックする場合はobjectOf
かshape
、もしくは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 を見てね、とのこと。
受け付ける例
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