🐾

【学習ログ】TypeScriptについて

2022/08/16に公開

型のつけ方

  1. 型推論
    型を指定しなくても、TypeScript側で勝手に推測して型をつけてくれること
  2. 型アノテーション
    プログラマー側で明示的に型をつける方法
  3. 型アサーション
    既にある型に対して上書きで型を付与するもの
    多用するべきものではない

型推論があるのにあえて型アノテーションを使うメリット

  • ドキュメント的な役割を果たす
  • コンパイル速度があがる
  • 型推論だけでは足りない場面がある

大きく分けてプリミティブ型とオブジェクトの2つがある

プリミティブ型

  1. 真偽値(Boolean)
  2. 文字列(String)
  3. 数値(Number)
  4. null
  5. undefined
  6. BigInt
  7. シンボル(Symbol)

Literal TypesとWidening

Literal Types

Boolean、String、Numberのプリミティブ型を細分化したもの
複数の取りうる値の中から特定の値に限定させたい時に便利

  1. Boolean Literal Types
    truefalseのどちらかのみを指定する
  2. String Literal Types
    具体的な文字列を指定する
  3. Number Literal Types
    具体的な数字を指定する
Widening

型が拡張されてしまうという性質のこと
例えば、const宣言で型推論されたLiteral Types(Widening Literal Types)が、再代入可能なものに代入された際に元の型に拡張されてしまうなど

オブジェクト

プリミティブ型以外のもの全て

  1. Array
    number[]またはArray<number>の形で指定する
    要素の型が複数の時は、|で区切って複数指定できる
    (number | string)[]またはArray<number | string>
  2. Tuple
    Arrayとよく似ているが、より厳格に指定できる
    要素の数が決まっていればTuple型を使った方が良い
    [string, number]
  3. Any
    型が不明な時に型チェックを無効にしてコンパイルを無理やり通すために使うもの
    型の恩恵を放棄することとなるため多用しない
  4. Unknown
    型が不明な時に使用するという点ではAnyと似ている
    なんでも代入できるが、利用時にはエラーを出してくれるため、Anyよりは安全に使用できる
  5. Void
    返り値がない関数の返り値の型として使用する
  6. Never
    発生する可能性のない値の型として使用する
  7. Object
    オブジェクトに対する指定方法は複数ある
// nullとundefined以外のプリミティブ型を受け取れてしまうため×
let obj1: {} = {};

// オブジェクト(プリミティブ型以外)をすべて受け取れてしまうため×
let obj2: object = {};

// 標準ライブラリを使った書き方○
let obj3: Record<string, unknown> = {};

// Index signatureを使った書き方○
// 特定のオブジェクトがある場合やネストする場合にこちらの方が便利
let obj4: {[key: string]: unknown} = {};

空オブジェクトということを定義したい場合は、unknownneverとすれば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

色々なことに応用できる汎用的な技術

  1. オブジェクトのプロパティ名を限定
  • 構文
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)の部分をkeyofkeyof typeofで、他の型や変数などから参照することができる
    オプショナルをなどをまとめて指定できる
  1. ジェネリクスと組み合わせて便利な型を作り出す
    (のちほど)

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