複数の型のpropsを受け取るコンポーネントの型定義

2021年7月10日

型を定義していると一例ですが、次のような場面に遭遇したことはないでしょうか?

const VideoPlayer: React.VFC<Props> = ({ url, manifestUrl }) //VideoPlayerはurl or manifestUrlで再生できる => { return ( //... 省略 ... ); }; <VideoPlayer url={url} manifestUrl={manifestUrl}/> //両方受け取りたくない //両方受け取った場合、TypeErrorを出したい

普通に何も考えずにpropsの型定義すれば以下のようになるでしょう。

type props = { url?:stiring; manifestUrl?: string; };

しかし、このように書いてしまうとurlmanifestUrl両方指定することができますよね。

両方指定した場合は、TypeErrorを出すようにしたいですね。

ということで、以下のように実装してみます。

type WithManifestUrl = { url?: never; manifestUrl: string; }; type WithUrl = { url: string; manifestUrl?: never; }; type Props = WithManifestUrl | WithUrl;

こうすることで

<VideoPlayer url={url} manifest={manifestUrl}> // Type '{ url: string; manifestUrl: string; }' is not assignable to type '(IntrinsicAttributes & A) | (IntrinsicAttributes & //B)'. // Type '{ url: string; manifestUrl: string; }' is not assignable to type 'B'. <VideoPlayer url={url} > //OK <VideoPlayer manifest={manifestUrl}> //OK

タイプエラーが出でくれましたね。

しかしこう書いたからといってVideoPlayerコンポーネント内でTypeScriptは「urlが定義されている場合は、manifestUrlundefinedだ」ということを普通には理解してくれません。

以下のように書いてみましょう

const MediaPlayer: React.VFC<Props> = (props) => { let content: string; if (props.url !== undefined) { content = props.url; } else { content = props.manifestUrl; } return ( <div className="App"> <h1>{content}</h1> </div> ); };

そうすることで、else以下のmanifestUrlstring型であることが型推論によって補完されています

ここでポイントなのは({url,manifestIrl}}としないことです。 Propsを展開してしまうと、manifestUrlとurlの型が確定しまい、props型での型推論のサポートがなくなり上のような挙動にはならないのです。

まとめ

指定したくない型をneverにするかundefinedにすればいいかはわからないので教えてくださいmm

短いですがおわり

参考文献