【学習ログ】TypeScriptについて
- しまぶーさんのサロンで学んだことについて自分用メモ
- 随時更新
型のつけ方
- 型推論
型を指定しなくても、TypeScript側で勝手に推測して型をつけてくれること - 型アノテーション
プログラマー側で明示的に型をつける方法 - 型アサーション
既にある型に対して上書きで型を付与するもの
多用するべきものではない
型推論があるのにあえて型アノテーションを使うメリット
- ドキュメント的な役割を果たす
- コンパイル速度があがる
- 型推論だけでは足りない場面がある
型
大きく分けてプリミティブ型とオブジェクトの2つがある
プリミティブ型
- 真偽値(Boolean)
- 文字列(String)
- 数値(Number)
- null
- undefined
- BigInt
- シンボル(Symbol)
Literal TypesとWidening
Literal Types
Boolean、String、Numberのプリミティブ型を細分化したもの
複数の取りうる値の中から特定の値に限定させたい時に便利
- Boolean Literal Types
true
かfalse
のどちらかのみを指定する - String Literal Types
具体的な文字列を指定する - Number Literal Types
具体的な数字を指定する
Widening
型が拡張されてしまうという性質のこと
例えば、const宣言で型推論されたLiteral Types(Widening Literal Types)が、再代入可能なものに代入された際に元の型に拡張されてしまうなど
オブジェクト
プリミティブ型以外のもの全て
- Array
number[]
またはArray<number>
の形で指定する
要素の型が複数の時は、|
で区切って複数指定できる
(number | string)[]
またはArray<number | string>
- Tuple
Arrayとよく似ているが、より厳格に指定できる
要素の数が決まっていればTuple型を使った方が良い
[string, number]
- Any
型が不明な時に型チェックを無効にしてコンパイルを無理やり通すために使うもの
型の恩恵を放棄することとなるため多用しない - Unknown
型が不明な時に使用するという点ではAnyと似ている
なんでも代入できるが、利用時にはエラーを出してくれるため、Anyよりは安全に使用できる - Void
返り値がない関数の返り値の型として使用する - Never
発生する可能性のない値の型として使用する - Object
オブジェクトに対する指定方法は複数ある
// nullとundefined以外のプリミティブ型を受け取れてしまうため×
let obj1: {} = {};
// オブジェクト(プリミティブ型以外)をすべて受け取れてしまうため×
let obj2: object = {};
// 標準ライブラリを使った書き方○
let obj3: Record<string, unknown> = {};
// Index signatureを使った書き方○
// 特定のオブジェクトがある場合やネストする場合にこちらの方が便利
let obj4: {[key: string]: unknown} = {};
空オブジェクトということを定義したい場合は、unknown
をnever
とすればOK
Intersection Types(交差型)
複数の型を&
でつないでひとつにまとめたもの
Intersection Typesで定義された型はすべてを満たさないとエラーとなる
type Foo = {
a: number;
b: string;
}
type Bar = {
c: boolean;
}
//
type FooBar = Foo & Bar;
Union Types(共用体型・合併型)
型を|
でつなぐことで、つないだ型の中の「どれかに一致していればOK」という状態となる
「必ずどれかの型に一致していないといけない」というわけでなく、複数の型に一致していてもよい
type Foo = {
a: number;
b: string;
}
type Bar = {
c: boolean;
}
//
type FooBar = Foo | Bar;
型の定義
Type Alias
type Foo = {
a: number;
};
- どんな型でも宣言できる
- open-ended(同じ名前で宣言した時に自動的にマージされること)に準拠していないため、同じ名前での宣言は許されない
- プロパティをオーバーライドする
- Mapped Typesが使える
type Bar = Foo & {
b: number;
};
Interface
interface Foo {
a: number;
}
- 宣言できるのはオブジェクトのみ
- open-endedに準拠している
- プロパティのオーバーライドをしない
- Mapped Typesが使えない
- extendsで型を継承できる
interface Bar extends Foo {
b: number;
}
(参考)Mappd Types
type Animals = "dog" | "cat";
type Foo = {
[key in Animals]: number;
};
型クエリ
型をキャプチャするもの
typeof
変数や関数など(型定義以外のもの)に使うもの
// 型アノテーションの場合
let foo: string;
console.log(typeof foo); // string
// 型推論の場合
let foo = "foo";
console.log(typeof foo); // string
const bar = "bar";
console.log(typeof bar); // "bar"
// 型アノテーションと型推論がある場合、型アノテーションが優先される
const foo:string = "foo";
console.log(typeof bar); // string
keyof
型定義に対して使うもの
オブジェクトのプロパティ名を Literal Types として一覧で取得する
type Obj = {
foo: string;
bar: number;
};
type Key = keyof Obj;
console.log(typeof Key); // "foo" | "bar"
ダウンキャストとアップキャスト
ダウンキャスト
型を厳しくすること
- 方法
const assertionなど
as const
をつけることで、そこに書かれている値の型で固定できる
オブジェクトの後ろに書くことで、まとめて固定することもできる
const theme = {
color: "red",
backgroundColor: "blue"
} as const;
- 使用例
Wideningの防止(例えば定数ファイル)
アップキャスト
型をゆるくすること
- 方法
anyなど - 使用例
型解決ができない時
基本的にやらない(ので覚えなくていい)
番外編
- Non-null assertion
undefind
の可能性がある時に!
をつけることでundefind
の可能性を消すことができるが、undefind
が入ってきた時にエラーとなるため、基本的に使うべきではない(型ガードで解決する方がよい) - Double assertion
as unknown as
とすることで、互換性のない型への変換が可能となるが、こちらも基本的に使うべきではない
IndexSignature と MappedTypes
IndexSignature
オブジェクトのプロパティを動的に追加したいときに使用する
type User = {
[key:string]: string
};
// key の部分は慣習
例えば上記の例だと、string型のvalueを持ったstring型のプロパティがなんでも追加できるため、便利な一面もあるが、型が脆くなるためあまり多用するべきものではない
MappedTypes
色々なことに応用できる汎用的な技術
- オブジェクトのプロパティ名を限定
- 構文
type PersonalData = {
[K in (UnionTypes)]: number;
};
// K の部分は慣習
type PersonalData = {
[K in "height" | "weight"]: number;
};
// 上記の書き方で、下記と同じ意味になる
type PersonalData = {
height: number;
weight: number;
};
// テクい使い方
type OptionalPersonalData = {
[K in keyof PersonalData]?: PersonalData[K]
};
- 便利な点
(UnionTypes)の部分をkeyof
やkeyof typeof
で、他の型や変数などから参照することができる
オプショナルをなどをまとめて指定できる
- ジェネリクスと組み合わせて便利な型を作り出す
(のちほど)
Type Guard
型の絞り込み
typeof
JSのメソッド
in演算子
Tagged Union
ユーザー定義のType Guard
Generics
型の決定を後回しにできるもの
type Foo<T> = {
value: T;
};
// T の部分は慣習
- 初期値の設定
type Foo<T = 型(初期値)> = {
value: T;
};
初期値を入れておくと、使用する際に型を指定しない場合に初期値の型として扱われる
- extendsによる型の制約
type Foo<T extends 型> = {
value: T;
};
extendsで型を指定することで、使用する際にその範囲内でしか型を指定できなくなる
- 初期値とextendsの合わせ技
type Foo<T extends 型 = 型(初期値)> = {
value: T;
};
- 関数のGenerics
const foo = <T>(arg:T) => {
return { value: arg };
};
関数のGenericsには暗黙的な型解決があるため、型引数を指定しない場合には引数の型から推論してくれる
型引数を指定するのは、引数がNullableかもしれない場合やUnionTypesの場合など
extendsで型を制約することによって、関数内で引数が使用できるメソッドを呼び出すことができて便利
- Lookup Types
type Obj = {
a: string
}
type Foo = Obj["a"]
Obj["a"]
の部分がLookup Typesで、オブジェクトのvalueの型にアクセスする
上記の例だとFoo
はstringになる
Discussion