プログラミングTypeScript 6章〜最後まで読んで

5 min read読了の目安(約4600字

この記事は、

https://zenn.dev/mato/articles/8b26e77c4cb7f8

の続きの記事になります。

流石に、本も後半になると難易度がぐっとが上がり、へぇの連続でした。

その中でもへぇレベルが高かったものをピックアップしていきます。

6章 高度な型

型のルックアップ

type APIResponse = {
    fishId: string
    similarFishList: {
        count: number
        fishes: {
            name: string
        }[]
    }
}

type FishList = APIResponse['similarFishList'] 

このようにオブジェクトみたいに、型を取得(ルックアップ)することができます。

keyof演算子を利用して、型安全なゲッター関数を実装する

これは、動きは分かるけど、モチベーションがいまいちわからなかった話。

type ResponseKeys = keyof APIResponse // 'fishId' | 'similarFishList'
type FishId = keyof APIResponse['similarFishList'] // 'count' | 'fishes'

// 型安全なゲッター関数を実装
function get<
    O extends object,
    K extends keyof O
>(
    o: O,
    k: K
): O[K] {
    return o[k]
}

let fishResponse: APIResponse = {
    fishId: 'asdfghjkl',
    similarFishList: {
        count: 2,
        fishes: [
            { name: 'tuna' },
            { name: 'shark' },
        ]
    }
}

let fishId = get(fishResponse, 'fishId') // let fishId: string
let fishId2 = fishResponse.fishId // let fishId2: string

fishId2のようにドット演算子でもstring型であることは分かるが、get関数を型安全でとてもメリットがあるらしいが、よくわからなかった orz

レコード型

type Weekday = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri'
type Day = Weekday | 'Sat' | 'Sun'

let nextDay: Record<Weekday, Day> = {
    Mon: 'Tue'
}

// Type '{ Mon: "Tue"; }' is missing the following properties from type 'Record<Weekday, Day>': Tue, Wed, Thu, Frits(2739)

レコードは完全性を担保しようと、エラー出してくれます。

マップ型

let nextDay: {[K in Weekday]: Day} = {
    Mon: 'Tue'
}

// Type '{ Mon: "Tue"; }' is missing the following properties from type '{ Mon: Day; Tue: Day; Wed: Day; Thu: Day; Fri: Day; }': Tue, Wed, Thu, Frits(2739)

インデックスシグネチャの型安全性をより高めた感じ

マップ型を使って、更なる制約を加える

type Role = {
    id: number
    policies: string[]
}

// 全てのフィールドを省略可能に
type OptionalRole = {
    [K in keyof Role]?: Role[K]
}

// 全てのフィールドを読み取り専用に
type ReadonlyRole = {
    readonly [K in keyof Role]: Role[K]
}

こんな形で型の拡張ができます。

コンパニオンオブジェクトパターン

Fish.tsにFishの型定義を書き

type WeightUnit = 'kg' | 'g'

export type Fish = {
    weightUnit: WeightUnit
    weightValue: number
}

export let Fish = {
    from(weightValue: number, weightUnit: WeightUnit): Fish {
        return {
            weightUnit,
            weightValue
        }
    }
}

関数を利用する側で、import

import { Fish } from './Fish'

// こっちのFishは型
let fish: Fish = {
    weightUnit: 'kg',
    weightValue: 100,
}

// こっちのFishは値
let otherFish = Fish.from(200, 'g')

TypeScriptでは、型と値で名前空間が異なるので、こんなことができる。

メリットは、型と値の情報をまとめることができる。
利用する側も一度にインポートできメリットあるのは分かるものの、強いメリットを今んところ感じないが、いつか感じるだろう。

コンパニオンオブジェクトパターン!

ユーザー定義型ガード

function isString(a: unknown): boolean {
    return typeof a === 'string'
}

function parseInput(input: string | number) {
    let formatedInput: string
    if (isString(input)) {
        formatedInput = input.toUpperCase()
    }
}
// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.ts(2339)

isString()trueの時点で、inputstring型確定なので、そのまま通るかと思いきや、TypeScriptでは異なるスコープの場合エラーとなる。

そんな時に使えるのが、ユーザー定義型ガード

function isString(a: unknown): a is string {
    return typeof a === 'string'
}

こうすることで型チェッカーに型を伝えてあげることができ、エラーもなくなる。

条件型で型レベルの三項演算子

type IsString<T> = T extends string ? true : false

type A = IsString<string> // true
type B = IsString<number> // false

Tstring型のサブタイプの場合、リテラル型としてtrue or false を宣言という、なんとも驚き。
これも、いつ使うんだろうと思いつつ、面白い。

7章 エラー処理

例外を返す

type Fish = {    
    isAged: boolean
}
class AlreadyAgedFishError extends Error {}

const age = (fish: Fish): Fish | AlreadyAgedFishError => {
    if (fish.isAged) {
        return new AlreadyAgedFishError('The fish already aged.')
    }
    fish.isAged = true
    return fish
}

let agedFish = age({isAged: false}) // Fish | AlreadyAgedFishError
if (agedFish instanceof AlreadyAgedFishError) {
    console.error(agedFish.message)
} else {
    console.info('The fish aged successfully')
}

こうすることで、想定されるエラーを関数シグネチャに含めれる。

8章 非同期プログラミングと並行、並列処理

PromiseやAsync、Awaitまでは分かるが、非同期ストリーム以降はよく理解できなかった。

9章 フロントエンドとバックエンドのフレームワーク

ReactとAngularが紹介されていたが、Reactはよく触っているので目新しいものはなし。
Angularは見慣れていないせいか、やっぱりReactが良いな〜という感想のみ。

10章 名前空間とモジュール, 11章 JavaScriptとの相互運用, 12章 TypeScriptのビルドと実行

さらさら〜っと読んだので、ちゃんとは理解できていないと思われる。

終わりに

ライブラリを使ってアプリを作っているだけだと、ここまで深く型の理解をすることがなかったので、非常に勉強になった。
特に、これまで苦手意識があったジェネリック型の理解が深まったのは大きい。

職場で「なんか型エラー出てるけど、よく分からない」と言ってる方にとても強くオススメできる本であった。

あとは、練習のために、プロコンをTypeScriptで取り組んでみようかと思った。

お腹すいたので、まいばすけっとに晩御飯の具材を書いに行ってきます。