prop-typesか、それに代わる何かが必要な件

これまでReactコンポーネントを書く時、コンポーネントの使われ方を縛るためにprop-typesを用いて動的なチェックをしていました。TypeScriptでは代わって静的な型チェックを利用してprop-typesを使わずとも利用方法を縛るということになってます。しかし、けど漏れる。

import * as React from 'react';
import * as Redux from 'redux';
import * as ReactRedux from 'react-redux';
import * as Enzyme from 'enzyme';

test('HOC prop-types', () => {
    type Props = { greeting: string }; // greetingは?にしてない必須プロパティ
    const Target: React.StatelessComponent<Props> = (props) => {
        return <div className="target">{props.greeting}</div>;
    };
    const Connected = ReactRedux.connect(
        state => state,
        dispatch => ({}),
    )(Target);
    // モックReducerをjest.fn()で作る
    const reducer = jest.fn().mockReturnValue({ /* greeting: 'hello' */ });
    const store = Redux.createStore(reducer, {});
    // HOCのため、Enzymeのmountでフルレンダリング
    const rendered = Enzyme.mount(
        <ReactRedux.Provider store={store}>
            <Connected />
        </ReactRedux.Provider>);
    expect(rendered.find('.target').text()).toBe('hello'); // 上のコメント外さないと不合格
});

上記のJestテストはコンパイルできるけど合格しません。コメントを取り払ってモックReducerがgreetingプロパティを返すようにするとテストが通ります。React.Componentのように自分で緩めちゃうのは前提が違うとして、react-reduxのconnectなどHigh Order Componentの子として使う時にはコンポーネントの定義をこのTargetコンポーネントのようにガチガチにしていても素で漏れます。

prop-typesも開発ビルド時にコンソールに警告を流してくれるだけですが、それでも静かにすまされるより良い。

また、TypeScriptで書いたモジュールコンポーネントを非TypeScript環境から使う場合には当然ゆるゆる。さてどうしたものでしょうか。prop-typesを二重に書くかと思ったけど、prop-typesの型定義が用意されていないみたい。まったく必要ないと思われているのかな?もしくは賢い、それに代わる何かがあるのか?Javaだとaptでやるような、TypeScriptコンパイラに差し込むプラグインが作れれば型情報から自動でプロテクトコードを挿入したりできるんでしょうけどね。

github.com

と、こんなのググったら出てきた。。。結構まどろっこしいな。でもio-tsね。。。

やっぱりあるんですか

Using the Compiler API · Microsoft/TypeScript Wiki · GitHub

あるよ、やっぱりASTとれるか。TypeScriptコンパイラがTypeScriptで書かれているってことだからなあ。

prop-typesを無理やり使えるかな?

https://github.com/facebook/prop-types/blob/8abb8d73c6365aac2b9f508a9c49b0934de78f0a/factoryWithTypeCheckers.js#L211

この関数を作ったクラスのコンストラクタとかで呼べばいいのじゃないだろうかと思って、以下のように書いたら怒られた。直接呼ぶんじゃなく、PropTypes. checkPropTypes()を呼べという。。。

import * as PropTypes from 'prop-types';

PropTypes.object.isRequired(props, 'yourName', 'TargetComponent');
// -> 警告される。動かない。

https://github.com/facebook/prop-types/blob/master/README.md#proptypescheckproptypes

じゃあやってみようかと思っても、@types/prop-typesの型定義ファイルに、checkPropTypesが無かった。

import * as PropTypes from 'prop-types';

// あるはずのcheckPropTypesの型を定義
declare module 'prop-types' {
    function checkPropTypes<T>(
        typeSpecs: PropTypes.ValidationMap<T>,
        values: T,
        location: string,
        componentName: string,
        getStack?: () => string,
    ): void;
}

// TypeScript用のプロパティ定義
type Props = { yourName: string };

// PropTypes用のプロパティ定義
const YourTypes = { yourName: PropTypes.string.isRequired };

// テスト対象のプロパティ
const props = { yourName: 'masataka_k' };

PropTypes.checkPropTypes<Props>(YourTypes, props, 'props', 'TargetComponent');

これで完璧!。PropTypes用のプロパティ定義をするのであれば、もっと簡単にECMAScriotで書く時と同様に、static propTypes = YourTypes; とだけで通常運転ですが、動的にチェックするプロパティ定義を組み立てることもできるのでこのテクニックは有用に思います。