1. ホーム
  2. reactjs

動的なキーを持つオブジェクトのPropTypesチェック

2023-07-10 20:23:33

質問

Reactには、PropTypesを使ってpropの値をチェックする方法がたくさんあります。私がよく使うのは React.PropTypes.shape({...}) . しかし、私は最近、内部に動的なキー/値を持つオブジェクトを持っている状況に出くわしました。各キーは文字列 (既知の形式) であるべきで、各値は int であるべきであることは分かっています。カスタムのプロップ検証関数を使っても、プロップのキーを知っていることを前提にしています。オブジェクト/図形のキーと値の両方が正しいことをチェックするためにPropTypesを使用するにはどうすればよいですか?

...
someArray: React.PropTypes.arrayOf(React.PropTypes.shape({
  // How to specify a dynamic string key? Keys are a date/datetime string
  <dynamicStringKey>: React.PropTypes.number
}))
...

そこでもう一度。少なくとも、各キーの値が数値であることをチェックしたい。理想を言えば、キー自体が正しい形式の文字列であることもチェックできればと思います。

どのように解決するのですか?

注意 : この回答は2015年に書かれたもので、現在のReactのバージョンは0.14.3です。あなたが今使っているReactのバージョンに当てはまるかもしれませんし、当てはまらないかもしれません。

それは興味深い質問ですね。あなたの質問から、あなたは次のように聞こえました。 のドキュメントでカスタムタイプチェッカーについて読んだようです。 Prop Validation (提案の検証) . 後世のために、ここに再現しておきます。

// 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('Validation failed!');
  }
}

タイプチェッカーを実装する場合、私はできるだけReactの組み込み型 チェッカーを実装する場合、私はできるだけReactの組み込み型チェッカーを使いたいと思っています。値が であるかどうかをチェックしたいので PropTypes.number を使うべきでしょう?でも を使えばいいんです。 PropTypes.number('not a number!') を実行するだけで を実行し、適切なエラーを得ることができればよいのですが、残念ながらそれは です。まず最初に理解すべきは...

タイプチェッカーの仕組み

タイプチェッカーの関数シグネチャは以下の通りです。

function(props, propName, componentName, location, propFullName) => null | Error

ご覧のとおり、すべてのプロップが第1引数として渡され、第2引数として テストするプロップの名前が第2引数として渡されます。最後の 最後の3つの引数は、有用なエラーメッセージを出力するために使われます。 は任意です。 componentName は自明である。 location のいずれかになります。 'prop' , 'context' または 'childContext' (私たちは 'prop' にのみ興味があります)、そして propFullName はネストした 小道具を扱うときのものです。 someObj.someKey .

この知識を持って、型チェッカを直接呼び出すことができるようになりました。

PropTypes.number({ myProp: 'bad' }, 'myProp');
// => [Error: Invalid undefined `myProp` of type `string` supplied
//     to `<<anonymous>>`, expected `number`.]

ほらね。すべての引数がないと、それほど便利ではありません。これはより良いです。

PropTypes.number({ myProp: 'bad' }, 'myProp', 'MyComponent', 'prop')
// => [Error: Invalid prop `myProp` of type `string` supplied
//     to `MyComponent`, expected `number`.]

配列の型チェッカー

ドキュメントに書かれていないことのひとつに、カスタムタイプ チェッカを PropTypes.arrayOf にカスタム型チェッカーを提供する場合、 それは各配列 の各要素に対して呼び出され、最初の二つの引数はそれぞれ配列そのものと はそれぞれ現在の要素のインデックスとなります。これで、型チェッカのスケッチを始めることができます。 型チェッカのスケッチを始めることができます。

function validArrayItem(arr, idx, componentName, location, propFullName) {
  var obj = arr[idx];

  console.log(propFullName, obj);

  // 1. Check if `obj` is an Object using `PropTypes.object`
  // 2. Check if all of its keys conform to some specified format
  // 3. Check if all of its values are numbers

  return null;
}

これまでのところ、それは常に null (を返します(これは有効なプロップを示します)。 を投げ入れました。 console.log を挿入して、何が起こっているのか覗いてみました。 これで このようにテストできます。

var typeChecker = PropTypes.arrayOf(validArrayItem);
var myArray = [ { foo: 1 }, { bar: 'qux' } ];
var props = { myProp: myArray };

typeChecker(props, 'myProp', 'MyComponent', 'prop');
// -> myProp[0] { foo: 1 }
//    myProp[1] { bar: 'qux' }
// => null

ご覧のように propFullNamemyProp[0] であり、最初の項目は myProp[1] である。

では、関数の3つの部分を肉付けしてみましょう。

1. チェックするのは obj がオブジェクトであるかどうかを PropTypes.object

これは最も簡単な部分です。

function validArrayItem(arr, idx, componentName, location, propFullName) {
  var obj = arr[idx];
  var props = {};
  props[propFullName] = obj;

  // Check if `obj` is an Object using `PropTypes.object`
  var isObjectError = PropTypes.object(props, propFullName, componentName, location);
  if (isObjectError) { return isObjectError; }

  return null;
}

var typeChecker = PropTypes.arrayOf(validArrayItem);
var props = { myProp: [ { foo: 1 }, 'bar' ] };
typeChecker(props, 'myProp', 'MyComponent', 'prop');
// => [Error: Invalid prop `myProp[1]` of type `string` supplied to
//     `MyComponent`, expected `object`.]

完璧だ!次は...

2. そのキーのすべてがある特定のフォーマットに適合しているかどうかをチェックする

質問の中で、「各キーは文字列であるべきだ」とありますが、JavaScriptのすべてのオブジェクトのキーは文字列です。 JavaScript のすべてのオブジェクトのキーは文字列です。 キーがすべて大文字で始まるかどうかをテストしたいとします。そのためのカスタム タイプチェッカーを作りましょう。

var STARTS_WITH_UPPERCASE_LETTER_EXPR = /^[A-Z]/;

function validObjectKeys(props, propName, componentName, location, propFullName) {
  var obj = props[propName];
  var keys = Object.keys(obj);

  // If the object is empty, consider it valid
  if (keys.length === 0) { return null; }

  var key;
  var propFullNameWithKey;

  for (var i = 0; i < keys.length; i++) {
    key = keys[i];
    propFullNameWithKey = (propFullName || propName) + '.' + key;

    if (STARTS_WITH_UPPERCASE_LETTER_EXPR.test(key)) { continue; }

    return new Error(
      'Invalid key `' + propFullNameWithKey + '` supplied to ' +
      '`' + componentName + '`; expected to match ' +
      STARTS_WITH_UPPERCASE_LETTER_EXPR + '.'
    );
  }

  return null;
}

単体でテストできる

var props = { myProp: { Foo: 1, bar: 2 } };
validObjectKeys(props, 'myProp', 'MyComponent', 'prop');
// -> myProp.Foo Foo
//    myProp.bar bar
// => [Error: Invalid key `myProp.bar` supplied to `MyComponent`;
//     expected to match /^[A-Z]/.]

素晴らしい! これを私たちの validArrayItem タイプチェッカーに統合してみましょう。

function validArrayItem(arr, idx, componentName, location, propFullName) {
  var obj = arr[idx];
  var props = {};
  props[propFullName] = obj;

  // Check if `obj` is an Object using `PropTypes.object`
  var isObjectError = PropTypes.object(props, propFullName, componentName, location);
  if (isObjectError) { return isObjectError; }

  // Check if all of its keys conform to some specified format
  var validObjectKeysError = validObjectKeys(props, propFullName, componentName);
  if (validObjectKeysError) { return validObjectKeysError; }

  return null;
}

そしてテストしてみてください。

var props = { myProp: [ { Foo: 1 }, { bar: 2 } ] };
var typeChecker = PropTypes.arrayOf(validArrayItem);
typeChecker(props, 'myProp', 'MyComponent', 'prop');
// -> myProp[0].Foo Foo
//    myProp[1].bar bar
// => [Error: Invalid key `myProp[1].bar` supplied to `MyComponent`;
//     expected to match /^[A-Z]/.]

そして最後に...

3. その値がすべて数字であるかどうかをチェックする

幸いなことに、ここで多くの作業をする必要はありません。 組み込みの PropTypes.objectOf :

// Check if all of its values are numbers
var validObjectValues = PropTypes.objectOf(PropTypes.number);
var validObjectValuesError = validObjectValues(props, propFullName, componentName, location);
if (validObjectValuesError) { return validObjectValuesError; }

以下、テストしてみます。

今すぐ全員集合

これが最終的なコードです。

function validArrayItem(arr, idx, componentName, location, propFullName) {
  var obj = arr[idx];
  var props = {};
  props[propFullName] = obj;

  // Check if `obj` is an Object using `PropTypes.object`
  var isObjectError = PropTypes.object(props, propFullName, componentName, location);
  if (isObjectError) { return isObjectError; }

  // Check if all of its keys conform to some specified format
  var validObjectKeysError = validObjectKeys(props, propFullName, componentName);
  if (validObjectKeysError) { return validObjectKeysError; }

  // Check if all of its values are numbers
  var validObjectValues = PropTypes.objectOf(PropTypes.number);
  var validObjectValuesError = validObjectValues(props, propFullName, componentName, location);
  if (validObjectValuesError) { return validObjectValuesError; }

  return null;
}

テスト用に簡単な関数を書いて、いくつかのデータを投げてみましょう。 を投げてみましょう。

function test(arrayToTest) {
  var typeChecker = PropTypes.arrayOf(validArrayItem);
  var props = { testProp: arrayToTest };
  return typeChecker(props, 'testProp', 'MyComponent', 'prop');
}

test([ { Foo: 1 }, { Bar: 2 } ]);
// => null

test([ { Foo: 1 }, { bar: 2 } ]);
// => [Error: Invalid key `testProp[1].bar` supplied to `MyComponent`;
//     expected to match /^[A-Z]/.]

test([ { Foo: 1 }, { Bar: false } ]);
// => [Error: Invalid prop `testProp[1].Bar` of type `boolean` supplied to
//     `MyComponent`, expected `number`.]

うまくいきましたね。これで、Reactコンポーネントでも 内蔵のタイプチェッカーと同じように

MyComponent.propTypes = {
  someArray: PropTypes.arrayOf(validArrayItem);
};

もちろん、もっと意味のある名前を付けて、独自のモジュールに移動することをお勧めします。 を独自のモジュールにすることをお勧めします。