Open4

[読書メモ]初めてのTypeScript(ISBN: 978-4-8144-0036-2)

くそぼうずくそぼうず

第I部 TypeScriptの概念

3章 合併型とリテラル型

  • 型の絞り込み(type narrowing): ある値が、定義された型、宣言された型、以前に推論された型よりも限定的な型であることをTypeScriptにコードで示すこと
  • 型ガード(type guard): 型の絞り込みのために利用できる論理チェックのこと
  • 構成オプション(13章)のstrictNullChecksを無効にすると、コード内に「| null | undefined」が追加され、どの変数もnullやundefinedを受け付けるようになる

4章 オブジェクト

  • TypeScriptの型システムは構造的型付け(structual typing)を採用している
  • プロパティの型アノテーションの中で「:」の前に「?」を含めることで、そのそれが省略可能なプロパティであることを表現できる
  • neverというキーワードと型は、プログラミング言語においてボトム型すなわち「空の型」と呼ばれるもの。ボトム型には取り得る値がなく、到達し得ない型であり、どんな型も割り当てることができない
くそぼうずくそぼうず

第II部 TypeScriptの概念の機能

6章 配列

  • 固定サイズの配列をタプル(tuple)と呼ぶ
  • constアサーション(as const)を使うと、読み取り専用のタプルになる

7章 インターフェース

  • インデックスシグネチャを使うとインターフェースのオブジェクトが任意のキーを受け取り、そのキーが特定の型を持つことを表現できる。
    • 任意のキーと値のペアを保管したい場合は、概して、Mapを使う方が安全
interface WordCounts {
  [i: string]: number;
}

const counts: WordCounts = {}

// 任意のキーと値を割り当てることができる
counts.apple = 0;
counts.banana = 1;
  • JavaScriptにおいて、number型のキーが暗黙的にstring型のキーに変換される。つまり、obj[1]obj["1"]という表現は等価
# 上記の理由からエラーになる。obj["1"]にはundifinedが割り当てることができないため
interface MoreNarrowStrings {
  [i: number]: string | undifined;
  [i: string]: string;
}

9章 型修飾子

  • ユニバーサル型はシステム内でとり得るすべての値を表現できる型(anyやunknown)
  • ユーザ定義型ガート(文法 => function typePredicate(input: WideType): input is NarrowType
  • 型演算子
    • keyof:既存の型を受け取り、その型で許されるすべてのキーの合併型を返す
    • typeof:与えられた値の型を返す
    • keyof typeof:ある値の型が受け入れるキーを簡潔に取得する
  • 型アサーション(type assertion) or 型キャスト(type cast):型システムによる値の型の解釈を上書きするための構文

10章 ジェネリック

// ジェネリック型 + タグ付き合併型
type Result<Data> = FailerResult | SuccessResult<Data>;

interface FailerResult {
    error: Error;
    success: false;
}

interface SuccessResult<Data> {
    data: Data;
    success: true;
}

function handleResult(result: Result<string>) {
    if (result.success) {
        console.log(`${result.data}`);
    } else {
        console.error(`${result.error}`);
    }
}
// keyofと制約付き型パラメータ
function get<T, Key extends keyof T>(container: T, key: Key) {
    return container[key];
}

const roles = { hoge: "", fuga: ""};
const hoge = get(roles, "hoge");
const foo = get(roles, "foo"); // Argument of type '"foo"' is not assignable to parameter of type '"hoge" | "fuga"'. のエラーが出る
// このようにすると返り値のかたはcontainerのすべてのプロパティの合併型になる(よくない)
function get<T>(container: T, key: keyof T) {
    return container[key];
}
  • 関数に型パラメータが必要かどうかを明らかにするためには、それが2回使われるかどうかを見る
  • 型パラメータの命名規則:T(type), K(key), V(value)。ややこしい場合は、1文字の略語の代わりに完全に記述された名前をつくことを検討すべき
くそぼうずくそぼうず

第IV部 課外講義

15章 型演算

  • ある型を別の型にマップするための構文がTypeScriptには用意されている。マップ型(mapped type)は、別の型を受け取り、その型のそれぞれのプロパティに対して何らかの演算を行う型
interface BirdVariants {
    dove: string;
    eagle: boolean;
}

// keyofを使ってBirdVariantsのそれぞれのメンバーにnullを追加したもの
// マップ型を使うと元の型から別の型にそれそれのフィールドをコピーする必要がなくなる
type NullableBirdVariants = {
    [K in keyof BirdVariants]: BirdVariants[K] | null;
}

// マップ型を使って修飾子を変更(オプションメンバーに変更)
// NOTE: Partial<T>が用意されている
type OptionalBirdVariants = {
    [K in keyof BirdVariants]?: BirdVariants[K];
}

// マップ型を使って修飾子を変更(readonlyを付与)
type ReadonlylBirdVariants = {
    readonly [K in keyof BirdVariants]: BirdVariants[K];
}

// 修飾子を削除する(修飾子の前に-を付与することで実現できる)
type NonOptionalBirdVariants = {
    [K in keyof OptionalBirdVariants]-?: OptionalBirdVariants[K];
}
  • 型の分配法則 => 「条件型<T | U>」は「条件型<T> | 条件型<U>」と同じ
// 分配法則の例
type ArrayIfUnlessString<T> = T extends string ? T : T[];
type HalfArrayfied = ArrayIfUnlessString<string | number> ; // string | number[]
  • inferによる型情報の取得(extends節の中でinferキーワードを使うことで、条件の任意の部分にアクセスできる)
// Tに対して新たにItemと名づけ、何らかの型の配列であるかどうかをチェックし、もしそうであればItemになり、そうでなければTになる
type ArrayItems<T> = T extends (infer Item)[] ? Item : T;

type StringItem = ArrayItems<string>; // => string
type StringArrayItem = ArrayItems<string[]>; // => string
type String2DItem = ArrayItems<string[][]>; // => string[]

// 再帰的に型の情報を取得する
type ArrayItemsRecursive<T> = T extends (infer Item)[] ? ArrayItemsRecursive<Item> : T;

type String2DItemRec = ArrayItemsRecursive<string[][]>; // => string[]
// Tに含まれる関数でないメンバーを関数に変換する
type MakeAllMenbersFunctions<T> = {
    // マップ型条件型
    [K in keyof T]: T[K] extends (...args: any[]) => any
    ? T[K]
    : () => T[K]
};

type MemberFunctions = MakeAllMenbersFunctions<{
    alreadyFunction: () => string,
    notYetFunction: number;
}>;
  • 交差型(&)の中でneverを使うと、その交差型は単にneverになる。合併型(|)に含まれるneverは無視される。
type OnlyStrings<T> = T extends string ? T : never;
type RedOrBlue = OnlyStrings<"red" | "blue" | 0 | false>;
// 型の分配法則 + 合併型のneverは無視される ことから、"red" | "blue" になる
  • ①neverは合併型の中で無視される、②マップ型は、型のメンバーをマップできる、③条件型を使って、条件に応じてneverに変えられる を組み合わせて、元の型のそれぞれのメンバーを、元のキーまたはneverに変更するマップ型を作成できる
// 文字列のメンバーのキーを抜き出す
type OnlyStringProperties<T> = {
    [K in keyof T]: T[K] extends string ? K : never;
}[keyof T];

interface AllEventData {
    participants: string[];
    location: string;
    name: string;
    year: number;
}

// never | "location" | "name" | never となり、neverは無視されるので"location" | "name"となる
type OnlyStringEventData = OnlyStringProperties<AllEventData>;
  • テンプレートリテラル型
type Brightness = "dark" | "light";
type Color = "blue" | "red";

type BrightnessAndColor = `${Brightness}-${Color}`;

let color: BrightnessAndColor = "dark-red";
type DateKey = "location" | "name" | "year";

type ExistenceChecks = {
    [K in `check${Capitalize<DateKey>}`]: () => boolean;
};

// これと同じ
// {
//     checkLocation: () => boolean;
//     checkName: () => boolean;
//     checkYear: () => boolean;
// }