投稿日:2021年5月15日
TypeScriptで便利な機能の一つにGenerics関数というものがあります。しかし、実はこのGenericsの表現は特定の条件下では言語のルール通り書いてもエラーになってしまいます。この記事ではtsxファイル内でのGenerics表現の注意点について詳しく解説しています。
TypeScriptで便利な機能の一つにGenerics関数というものがあります。
しかし、実はこのGenericsの表現は特定の条件下では言語のルール通り書いてもエラーになってしまいます。
この記事ではtsxファイル内でのGenerics表現の注意点について詳しく解説しています。
この記事は以下の環境で確認されました。
実際のコードを通じて一つずつ確認していきましょう。
まずはfunction宣言を使った以下のようなGenericsの関数定義があるとします。
function SampleFunc<T>(sampleValue:T){
console.log(`value : ${sampleValue} \r\ntype : ${typeof sampleValue}`);
}
この様にfunction宣言を使った関数定義は.ts、.tsxどちらに書いても問題なく動作します。
では次に同じ機能をもつ関数をアロー関数で定義してみます。
const SampleFunc = <T>(sampleValue: T) => {
console.log(`value : ${sampleValue} \r\ntype : ${typeof sampleValue}`);
};
コード自体は特に難しいところはありません。
この記述は.tsファイルにこのような記述をした場合には問題なく動作します。しかし、この記述を.tsxファイルに持っていった場合にはいきなりエラーになってしまいます。
発生するエラーが以下のようなものです。
JSX element 'T' has no corresponding closing tag.ts(17008)
Cannot find name 'T'
エラーの内容は以下のようなことを言ってます。
JSXでは山括弧(<>)で囲まれたものをJSXのタグとして認識します。一方、TypeScript側は山括弧で(<>)囲まれたものをGeneric関数の型引数として認識します。
.tsxはTypeScript内でJSXを使える様にしたものなので、function宣言を使っていない山括弧はこの2つの定義がぶつかってしまっているということですね。
※これが放置されているのはなぜなのか…
考え方としてはTypeScriptのGenericsの型引数の表現にはあってJSXのタグ表現にはない書き方をすると型引数として認識してくれます。
ここには2つ解決方法を書いておきます
TypeScriptのGeneric関数の型引数はカンマで区切ることで複数の型を使うことができます。JSXにはタグ表現にカンマを含めることができないため、型引数として認識してくれます。
const SampleFunc = <T, K>(sampleValue: T, sampleKey: K) => {
console.log(`value : ${sampleValue} \r\nkey : ${sampleKey}`);
};
TypeScriptではトレイリングカンマが許可されているため、以下のように書くことで一つしかない型引数も問題なく記述できます。
const SampleFunc = <T,>(sampleValue: T) => {
console.log(`value : ${sampleValue} \r\ntype : ${typeof sampleValue}`);
};
TypeScriptはextendsで型引数の型に制限をつけることができます。当然JSXにこのような表現はないため型引数として認識してくれます。
const SampleFunc = <T extends string>(sampleValue: T) => {
console.log(`value : ${sampleValue} \r\ntype : ${typeof sampleValue}`);
};
ここではTがstringをの定義を含んでいるという制限をつけました。
しかしこれはTに制限をつけてしまっているため、元の関数定義と異なっています。元の関数と同じ動作にさせたい場合には、『型の制限自体はあるが一切意味をなさない』という状況を作ってあげればOKです!typescriptの型には継承関係があり、その最もルートに位置するのがunknownという型です。すべての型は必ずunknownを継承しているため以下のように書くことでTの制限を実質無いものとすることができます。
const SampleFunc = <T extends unknown>(sampleValue: T) => {
console.log(`value : ${sampleValue} \r\ntype : ${typeof sampleValue}`);
};
アロー関数でGenerics書くたびに謎な表記を書きたくないので早くデフォルトの対応を入れてほしいですね〜