ジェネリックの入力をいくつかのタイプの1つに制限しようとしています。私が見つけた最も近い表記は、共用体タイプを使用しています。ここに簡単な例があります:
interface IDict<TKey extends string | number, TVal> {
// Error! An index signature parameter type must be
// a 'string' or a 'number'
[key: TKey]: TVal;
}
declare const dictA: IDict<string, Foo>;
declare const dictB: IDict<number, Foo>;
この例で私が探しているのは、TKey
はstring
またはnumber
のいずれかでなければならないが、それらの和集合ではないという方法です。
考え?
注:これは、幅広い質問の特定のケースです。たとえば、text
またはstring
(解析済みMarkdown)のいずれかであるStructuredText
を受け入れ、それを変換して、正確に返す関数がある別のケースがあります。対応するタイプ(サブタイプではない)。
function formatText<T extends string | StructuredText>(text: T): T {/*...*/}
技術的にはオーバーロードとしてそれを書くことができましたが、それは正しい方法のようには思えません。
function formatText(text: string): string;
function formatText(text: StructuredText): StructuredText;
function formatText(text) {/*...*/}
オーバーロードはunionタイプを受け入れないため、問題があることもわかります。
interface StructuredText { tokens: string[] }
function formatText(txt: string): string;
function formatText(txt: StructuredText): StructuredText;
function formatText(text){return text;}
let s: string | StructuredText;
let x = formatText(s); // error
2019-06-20のTS3.5 +用に更新
K extends string | number
:ええ、これは非常に満足できる方法で行うことはできません。いくつかの問題があります。 1つ目は、TypeScriptが認識するのは、2つの直接インデックスシグネチャタイプである[k: string]
と[k: number]
だけです。それでおしまい。これらの和集合([k: string | number]
なし)、またはそれらのサブタイプ([k: 'a'|'b']
なし)、またはそれらのエイリアスでさえできません:(no [k: s]
where type s = string
)。
2番目の問題は、インデックスタイプとしてのnumber
が奇妙な特殊なケースであり、残りのTypeScriptに一般化できないことです。 JavaScriptでは、すべてのオブジェクトインデックスは、使用される前に文字列値に変換されます。つまり、a['1']
とa[1]
は同じ要素です。したがって、ある意味では、インデックスとしてのnumber
タイプは、string
のサブタイプに似ています。 number
リテラルをあきらめて、代わりにstring
リテラルに変換することをいとわない場合は、より簡単な方法があります。
その場合は、 マップされたタイプ を使用して、必要な動作を取得できます。実際、Record<>
と呼ばれるタイプがあります 標準ライブラリに含まれています これは、私が使用することをお勧めします:
type Record<K extends string, T> = {
[P in K]: T;
};
type IDict<TKey extends string, TVal> = Record<TKey, TVal>
declare const dictString: IDict<string, Foo>; // works
declare const dictFooBar: IDict<'foo' | 'bar', Foo>; // works
declare const dict012: IDict<'0' | '1' | '2', Foo>; // works
dict012[0]; // okay, number literals work
dict012[3]; // error
declare const dict0Foo: IDict<'0' | 'foo',Foo>; // works
仕事にかなり近い。だが:
declare const dictNumber: IDict<number, Foo>; // nope, sorry
number
を機能させるための欠落している部分は、numericString
のような型で、
type numericString = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7' // ... etc etc
次に、IDict<numericString, Foo>
を使用して、IDict<number, Foo>
のように動作させることができます。そのような型がないと、TypeScriptに強制的にこれを実行させようとしても意味がありません。非常に説得力のあるユースケースがない限り、あきらめることをお勧めします。
ここで何が欲しいのか理解できたと思います。アイデアは、string | number
のような共用体を拡張する型の引数を取る関数が必要であるということですが、その共用体の1つ以上の要素に拡張された型を返す必要があります。サブタイプの問題を回避しようとしています。したがって、引数が1
の場合、1
の出力をコミットせず、number
のみを使用します。
これまでは、オーバーロードを使用するだけでした。
function zop(t: string): string; // string case
function zop(t: number): number; // number case
function zop(t: string | number): string | number; // union case
function zop(t: string | number): string | number { // impl
return (typeof t === 'string') ? (t + "!") : (t - 2);
}
これはあなたが望むように動作します:
const zopNumber = zop(1); // return type is number
const zopString = zop('a'); // return type is string
const zopNumberOrString = zop(
Math.random()<0.5 ? 1 : 'a'); // return type is string | number
そしてそれはあなたがあなたの組合に2つのタイプしかない場合に私が与えるだろう提案です。ただし、ユニオンの要素の空でないサブセットごとに1つのオーバーロードシグネチャを含める必要があるため、大きなユニオン(たとえば、string | number | boolean | StructuredText | RegExp
)の場合、扱いにくくなる可能性があります。
オーバーロードの代わりに conditional types を使用できます:
// OneOf<T, V> is the main event:
// take a type T and a Tuple type V, and return the type of
// T widened to relevant element(s) of V:
type OneOf<
T,
V extends any[],
NK extends keyof V = Exclude<keyof V, keyof any[]>
> = { [K in NK]: T extends V[K] ? V[K] : never }[NK];
以下にその仕組みを示します。
declare const str: OneOf<"hey", [string, number, boolean]>; // string
declare const boo: OneOf<false, [string, number, boolean]>; // boolean
declare const two: OneOf<1 | true, [string, number, boolean]>; // number | boolean
そして、これがあなたの関数を宣言する方法です:
function zop<T extends string | number>(t: T): OneOf<T, [string, number]>;
function zop(t: string | number): string | number { // impl
return (typeof t === 'string') ? (t + "!") : (t - 2);
}
そしてそれは以前と同じように動作します:
const zopNumber = zop(1); // 1 -> number
const zopString = zop('a'); // 'a' -> string
const zopNumberOrString = zop(
Math.random()<0.5 ? 1 : 'a'); // 1 | 'a' -> string | number
ふew。お役に立てば幸いです。幸運を!