[TypeScript] 2.8¶
TypeScript2.8のリリース内容まとめ
Conditional Types¶
HAD BETTER NORMAL
他の型が満たす条件によって、型の切り替えができる。
T extends U ? X : Y
と書いて UにTを割り当て可能ならX、そうでなければY 型 という意味。
JavaScriptで書かれたある規則に従って動く関数のInterfaceを明確にするもの という位置づけだと個人的には思っている。
以下のような国籍と、そのアシスタントを定義した場合..
interface Japanese {
myNo: number;
name: string;
}
interface American {
name: string;
}
class JapaneseAssistant {
private _japaneseAssistantBrand!: never;
hello(human: Japanese): void {
console.log(`こんにちは。${human.name}さん`);
}
}
class AmericanAssistant {
private _americanAssistantBrand!: never;
hello(human: American): void {
console.log(`Hello! ${human.name}!`);
}
}
国籍から最適なアシスタントを判定するConditional Type Assistant<T>
の実装は以下。
type Assistant<T> = T extends Japanese
? JapaneseAssistant
: T extends American
? AmericanAssistant
: unknown;
第1引数として指定された国籍から、予想されるアシスタント型を第2引数で推論するassist
関数の実装は以下。
function assist<T extends Japanese | American>(
human: T,
assistant: Assistant<T>
) {
// javascriptで頑張っている実装とかがあるはず..
}
tscやIDEで推論およびエラーを出してくれる。
const beaton: American = { name: "beaton" };
// 第1引数にAmerican型のObjectを入れることで、第2引数はAmericanAssistantとみなされる
assist(beaton, new AmericanAssistant());
// 第2引数にJapaneseAssistantを入れるとエラーになる
assist(beaton, new JapaneseAssistant());
Distributive conditional types¶
HAD BETTER HARD
Conditional typesにUnion typesが指定された場合、適切に分散する。
具体的には、Union typesで指定されたそれぞれの型について条件判定を行う。
// T1はstringとbooleanそれぞれに対して↑の判定が行われる
// stringは"STR"、booleanは"BOOL"のため "STR" | "BOOL" 型と判定される
type T1 = TypeName<string | boolean>
TypeName<T>
の定義は以下の通り。
type TypeName<T> = T extends string
? "STR"
: T extends number
? "NUM"
: T extends boolean
? "BOOL"
: T extends undefined
? "NONE"
: T extends Function
? "FUNC"
: "OBJ";
Conditional typesは再帰を許容しない。
確かどこかのバージョンで許容するようになったはず。。
いくつかサンプルコードを紹介する。
アサイン可能な型のみフィルターするFilter型¶
type Filter<T, U> = T extends U ? T : never;
TがUnion Typesのとき、Filter<T, U>
はUにアサイン可能な型のみをTから抽出した型である。
たとえば、以下のT1
はboolean
となる。
type T1 = Filter<number | string | boolean, Boolean | object>
// T1 = boolean
Tに指定されたUnion typesの型(number | string | boolean
)はそれぞれについてConditional typesとして判定されるため
T | U | T extends U ? | 結果 |
---|---|---|---|
number | Boolean │ object | false | never |
string | Boolean │ object | false | never |
boolean | Boolean │ object | true | boolean |
よって boolean
になる。
アサイン不可能な型のみ抽出するDiff型¶
Filter
型の逆。
type Diff<T, U> = T extends U ? never : T;
TがUnion Typesのとき、Diff<T, U>
はUにアサイン不可能な型のみをTから抽出した型である。
たとえば、以下のT1
はnumber | string
となる。
type T2 = Diff<number | string | boolean, Boolean | object>
// T2 = number | string
Tに指定されたUnion typesの型(number | string | boolean
)はそれぞれについてConditional typesとして判定されるため
T | U | T extends U ? | 結果 |
---|---|---|---|
number | Boolean │ object | false | number |
string | Boolean │ object | false | string |
boolean | Boolean │ object | true | never |
よって number | string
になる。
null/undefinedを除外した型NonNullable¶
先ほど作成したDiff
型を使う。
type NonNullable<T> = Diff<T, null | undefined>;
関数のプロパティ名をUnion typesとして成すFunctionPropertyName型¶
type FunctionPropertyName<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
具体例からイメージを沸かせるためHuman
インタフェースを定義し、それを交えて説明する。
interface Human {
id: number;
name: string;
hello(): void;
goodBye(): boolean;
}
keyof and Lookup Typesである[keyof T]
を外して、まずはMapped Typesの解析をする。
{
[K in keyof T]: T[K] extends Function ? K : never;
}
T
にHuman
を割り当て、K
を解くと以下の様になる。
{
id: Human["id"] extends Function ? "id" : never;
name: Human["name"] extends Function ? "name" : never;
hello: Human["hello"] extends Function ? "hello" : never;
goodBye: Human["goodBye"] extends Function ? "goodBye" : never;
}
よって
{
id: never;
name: never;
hello: "hello";
goodBye: "goodBye";
}
これに[key of T]
をつけると..
{
id: never;
name: never;
hello: "hello";
goodBye: "goodBye";
}[keyof Human]
よって "hello" | "goodBye"
になる。
Type inference in conditional types¶
HAD BETTER HARD
Conditional typesでマッチングしたパターンから、一部の型をinfer
でキャプチャできる。
具体例を見た方が分かりやすい。
Promiseで包まれた型を取り出すUnwrapPromise¶
type UnwrapPromise<T> = T extends Promise<infer U> ? U : any;
これは以下のように解釈できる。
UnwrapPromise<T>
はT
がPromise<U>
に割り当て可能なら(このケースではPromiseなら)、U
型- そうでなければ
any
型
infer U
がPromise<U>
というパターンにマッチするU
をキャプチャする
具体例。
type R = UnwrapPromise<Promise<number>>
// => number
type S = UnwrapPromise<number>
// => any (Promiseで包まれていないのでパターンにマッチしない)
関数のReturnする型を取得するMyReturnType¶
type MyReturnType<T> = T extends (...arg: any[]) => infer R ? R : any;
これは以下のように解釈できる。
MyReturnType<T>
はT
が(...arg: any[]) => R
に割り当て可能なら(このケースでは関数なら)、R
型- そうでなければ
any
型
infer R
が(...arg: any[]) => R
というパターンにマッチするR
をキャプチャする
具体例。
type R = MyRetrunType<Math.atan2>
// => number
type S = MyRetrunType<number>
// => any (関数ではないのでパターンにマッチしない)
co-variantの位置関係にある同一な複数型における推論¶
{ a: infer U, b: infer U }
のような共変位置にある場合、推論結果はUnion typeになる。
interface A {
p1: "x" | "y";
p2: "y" | "z";
}
type Property<T> = T extends { p1: infer U, p2: infer U } ? U : never;
type N = Property<A>;
// => "y" | "z" | "x"
contra-variantの位置関係にある同一な複数型における推論¶
{ a: (x: infer U) => void, b: (x: infer U) => void }
のような反変位置にある場合、推論結果はIntersection typeになる。
interface A {
p1: (arg: "x" | "y") => void;
p2: (arg: "y" | "z") => void;
}
type Property<T> = T extends { p1: (arg: infer U) => void, p2: (arg: infer U) => void } ? U : never;
type N = Property<A>;
// => "y"
Predefined conditional types¶
HAD BETTER EASY
Conditional typesを利用した新しい型が増えた。
型 | 説明 |
---|---|
Exclude |
TからUに割り当て可能な型を除外する |
Extract |
TからUに割り当て可能な型を抽出する |
NonNullable |
Tからnull/undefinedの可能性を除外する |
ReturnType |
関数が返却する型 |
InstanceType |
コンストラクタが作成する型 |
具体例。
type Excluded = Exclude<"a" | "b" | "c", "b" | "c">
// "a"
type Extracted = Extract<"a" | "b" | "c", "b" | "c">
// "b" | "c"
type NonNullableString = NonNullable<string | null | undefined>
// string
type AbsReturnType = ReturnType<typeof Math.abs>
// number
class Human {
constructor(public id: number, public name: string) { }
}
type HumanInstanceType = InstanceType<typeof Human>
// Human
Improved control over mapped type modifiers¶
HAD BETTER EASY
Mapped typesでreadonly
や?
が外せるようになった。
今までもできたこと¶
interface Human {
readonly myNo: number;
name: string;
favorite?: string;
}
// すべてのプロパティをOptionalにする
type ExOptional<T> = { [P in keyof T]?: T[P] };
/*
🧑🎓 T = Humanの場合
interface ExOptional<Human> {
readonly myNo?: number;
name?: string;
favorite?: string;
}
*/
// すべてのプロパティを変更不可にする
type ExReadonly<T> = { readonly [P in keyof T]: T[P] };
/*
🧑🎓 T = Humanの場合
interface ExReadonly<Human> {
readonly myNo: number;
readonly name: string;
readonly favorite?: string;
}
*/
今まではできなかったこと¶
今回できるようになったこと。
readonly
や?
の前に-
を付けると削除になる+
を付けると追加だが省略しても同じ
interface Human {
readonly myNo: number;
name: string;
favorite?: string;
}
// すべてのプロパティをRequiredにする
// ?を消すだけでなく、A | undefined => A のようにundefinedも消す
type ExRequired<T> = { [P in keyof T]-?: T[P] };
/*
🧑🎓 T = Humanの場合
interface ExRequired<Human> {
readonly myNo: number;
name: string;
favorite: string;
}
*/
// すべてのプロパティを変更可能にする
type ExMutable<T> = { -readonly [P in keyof T]: T[P] };
/*
🧑🎓 T = Humanの場合
interface ExMutable<Human> {
myNo: number;
name: string;
favorite?: string;
}
*/
Improved keyof with intersection types¶
HAD BETTER EASY
key of
にintersection typesを指定したとき、union typesに変換されるようになった。
具体的には以下のようなtype A, Bが存在するとき..
type A = { a: string };
type B = { b: number };
keyof (A & B)
は keyof A | keyof B
と変換され "a" | "b"
とみなされる。
Better handling for namespace patterns in .js files¶
UNKNOWN CAN NOT UNDERSTAND
JavaScriptファイル内で、トップレベルに宣言された空Objectはnamespaceと見なされるようになった。
var ns = {}; // namespaceと見なされる
どこで使うのか分からん。。
IIFEs as namespace declarations¶
UNKNOWN EASY
関数、クラス、空Objectを返す即時関数は名前空間として扱われる。
どこで使うのか分からん。。
Defaulted declarations¶
UNKNOWN CAN NOT UNDERSTAND
変更内容、メリット含めて全く分からない。。
Prototype assignment¶
NOT NECESSARY EASY
Objectリテラルを直接prototypeプロパティに代入できるようになった。
C.prototype.func = function() { ... };
C.prototype = {
func() { ... };
};
Nested and merged declarations¶
UNKNOWN CAN NOT UNDERSTAND
変更内容、メリット含めて全く分からない。。
Per-file JSX factories¶
UNKNOWN EASY
ファイルの先頭に@jsx dom
プラグマを使用することで、ファイル毎にJSXファクトリを変更できる。
※ 普段JSXを使用しないので使い所は分からず..
Locally scoped JSX namespaces¶
UNKNOWN EASY
名前空間JSX
がglobalではなくjsxNamespaceの下で検索されるようになった。
jsxNamespaceに何も定義されていない場合は、下位互換性を維持するためglobalのJSX
が使われる。
New --emitDeclarationOnly¶
HAD BETTER EASY
tsc
に--emitDeclarationOnly
オプションをつけると、型ファイル(.d.ts
)だけを生成する。
※ つまり、*.js
や*.jsx
を生成しない
別途--declaration
オプションの指定が必要。つけないとエラーになる。
transpileの手段にbabelなどを使いたいケースに使える。