TypeScriptの型関連についてメモ
Decorator
PythonのDecoratorと同じっぽい
Experimentalなのでコンパイルオプションが必要。
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Utility Types
-
TypeScript: Documentation - Utility Types
Built inの便利な型。
Partial<Type>
Readonly<Type>
Record<Keys,Type>
Pick<Type, Keys>
Omit<Type, Keys>
Exclude<Type, ExcludedUnion>
Extract<Type, Union>
NonNullable<Type>
Parameters<Type>
ConstructorParameters<Type>
ReturnType<Type>
InstanceType<Type>
Required<Type>
ThisParameterType<Type>
OmitThisParameter<Type>
ThisType<Type>
Genericsの型パラメータからインスタンス化する
-
Why can't I write typeof T, new T, or instanceof T in my generic function?
FAQ · microsoft/TypeScript Wiki
Generics are erased during compilation.
型パラメータの情報はコンパイルされるときに消えちゃう(Javaと同じぽい)ので、型パラメータを利用したインスタンス化できない。以下のような回避策がある。
function create<T>(ctor: { new(): T }) {
return new ctor();
}
var c = create(MyClass); // c: MyClass
function isReallyInstanceOf<T>(ctor: { new(...args: any[]): T }, obj: T) {
return obj instanceof ctor;
}
Mixin
ちょっと思ってたものと違う感じ。
Decoratorでは使えないのがちょっと残念。
You cannot use decorators to provide mixins via code flow analysis:
interfaceのデフォルト実装はできない
-
Abstract Classes
TypeScript: Handbook - Classes - typescript default function inside interface - Stack Overflow
abstract class
を使う。
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("roaming the earth...");
}
}
never
never - TypeScript Deep Dive 日本語版
-
any
がtop型 -
never
がbottom型
常に throw
するとか、絶対 return
しないとか、そういうときに使う。Kotlinと同じぽい。
function foo(x: string | number): boolean {
if (typeof x === "string") {
return true;
} else if (typeof x === "number") {
return false;
}
// Without a never type we would error :
// - Not all code paths return a value (strict null checks)
// - Or Unreachable code detected
// But because TypeScript understands that `fail` function returns `never`
// It can allow you to call it as you might be using it for runtime safety / exhaustive checks.
return fail("Unexhaustive!");
}
function fail(message: string): never { throw new Error(message); }
keyof
プロパティ名のユニオン型が取得できる。
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string
最後の K3
はPlaygroundでやってみると string | number
になっていた。
Lookup types
keyof
と一緒に導入された。
型に対して配列のようにプロパティ名を渡すと相当する型が返ってくる。
interface Person {
name: string;
age: number;
location: string;
}
type P1 = Person["name"]; // string
type P2 = Person["name" | "age"]; // string | number
type P3 = string["charAt"]; // (pos: number) => string
type P4 = string[]["push"]; // (...items: string[]) => number
type P5 = string[][0]; // string
プロパティ名というか、メンバー関数名を指定すると、関数の型が返ってくる。
ValueOf<T>
type V = Person[keyof Person]; // string | number
keyof
と Lookup Types
の組み合わせで値のみの型が取得できる。
これを利用して Valueof<T>
という型を作ることができる。
type ValueOf<T> = T[keyof T];
type V2 = ValueOf<Person>;
keyof
が小文字なので valueof
としたいけど型だから Valueof
にした方が良いのかどうか。
Types as Sets
なんか地味に大事なことが書いてあった。
TypeScript provides a number of mechanisms to work with types in a set-theoretic way, and you’ll find them more intuitive if you think of types as sets.
型を集合として考えると良いらしい。
Erased Structural Types
TypeScriptでは実行時に型の情報はなくなってしまう。
Because TypeScript’s type system is fully erased, information about e.g. the instantiation of a generic type parameter is not available at runtime.
class
もそうなのかな?
class Car {};
var c = new Car();
console.log(typeof c); // "object"
console.log(c instanceof Car); // true
class Fish {};
console.log(c instanceof Fish); // false
console.log(c instanceof Object); // true
instanceof
は識別できるらしい。
Index signature(インデックス型)
JavaScript では以下のようなObjectを辞書のようにして利用できる。
var v = {a:1};
console.log(v.a); // 1
console.log(v['a']); // 1
↑ v['a']
の 'a'
の部分をインデックスシグネチャというらしい。
JavaScriptでは object
を利用できるが、TypeScriptでは string
か number
しか利用できない。
インデックスシグネチャは定義しないと使えない
interface Foo {
x: number;
y: number;
//[index: string]: number;
}
var foo: Foo = {x: 10, y: 20};
console.log(foo['x']); // ここはエラーにならない
var x = 'x';
console.log(foo[x]); // ここでエラー
↑このようにインデックスシグネチャとして利用できるプロパティの宣言をしていないと、変数を利用したインデックスシグネチャの利用でエラーになる。
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Foo'. No index signature with a parameter of type 'string' was found on type 'Foo'.
interface Foo {
x: number;
y: number;
[index: string]: number;
}
var foo: Foo = {x: 10, y: 20};
var x = 'x';
console.log(foo[x]); // 10
これだとエラーにならない。
Type Aliases と Interface の違い
大きな違いは以下っぽい。
-
Interface
はオープン、type
はクローズ -
MappedType
が使えないか使えるか
なんとなく type
を使っていた方が意図しない型の挙動がなさそう…
Arrayが持つ要素の型を定義する
Array<T>
があったときにそこから T
を定義することができる。
type Arr = string[];
type str = Arr[0]; // type str = string
type str2 = Arr[number]; // type str2 = string
過去に調べた Lookup Types に似ているけど、こちらは Index Sigunature
の方になる。
Generic parameter defaults
ジェネリクスを使用する際に型パラメータのデフォルト値を設定することができる。
そこで、デフォルト型パラメーターとしてErrorを指定することでジェネリクスの型Tは必要な時だけ指定して、何も指定してない場合は自動でErrorとする事ができます。
type MyErrorEvent<T = Error> = {
error: T;
type: string;
}
// デフォルト型パラメータを指定した事で Error の型指定を省略できる
const errorEvent: MyErrorEvent = { error: new Error('エラーです'), type: 'syntax' }
const networkErrorEvent: MyErrorEvent<NetworkAccessError> = { error: new NetworkAccessError('ネットワークエラーです'), type: 'nextwork' }
extends
とも併用できるので以下のようにも記述できる。
MyErrorEventをに与えられる型TをErrorのサブクラスに限定しつつ、省略時はSyntaxErrorとしたい場合は次のような書き方になります。
type MyErrorEvent<T extends Error = SyntaxError> = {
error: T;
type: string;
}