🐟

プログラミングTypeScript 1章〜5章を読んで

2021/01/09に公開

こんにちは、matoです。

  1. 最近、本サービスであるzenn.devを利用してる方が多いなと感じ、試しにどんなものか書いてみたい。
  2. TypeScriptは、React Nativeでスマホアプリを開発する上で、最近書き始めたが、まとまった勉強なしでコードを書いていたので、TypeScript力を上げるべく本を読んだが、せっかくなのでなんらかアウトプットしたい
  3. 一冊370ページ全部読んでから記事を書こうと思ったが、途中で挫折したため、一旦5章までとした。

という想いと経緯で、本記事を書くに至りました。

本は、こちらです。(一応アフィリリンクです)

https://amzn.to/3saDOFO

自分の考えるTypeScriptのざっくりメリット

JavaScriptの場合、実行時にエラーでツラいのですが、TypeScriptは型付になるのでトランスコンパイル時にエラーになり早く間違いに分かったり、静的解析でエディタ上でもエラーを表示させることができたり、型による自動補完がきいてコードをサクサク書けて最高の気分になります。

ここからは、本の中で改めてこれ便利だよな〜とか、これ知らなかった!というのをピックアップ形式で書いてきます。

リテラル型

// before
type FishName = string
let fishName: FishName = 'Dalmatian' // no error
// after
type FishName = 'Tuna' | 'Shark' | 'SeaPearch'
let fishName: FishName = 'Dalmatian'
// Type '"Dalmatian"' is not assignable to type 'FishName'.ts(2322)

ということで、エラーにしてくれます。
ちなみにこんな使い方も。

type IsFish = true
let isFish: IsFish = false
// Type 'false' is not assignable to type 'true'.ts(2322)

当たり前なのですが、booleanのvalueもリテラル型にできます。

インデックスシグネチャ

let aquarium: {
    [fishName: string]: number
} = {
    'NeonTetra': 10,
    'CardinalTetra': 10,
    'CorydorasPanda': 5,
    'RummyNoseTetra': '10',
    // Type 'string' is not assignable to type 'number'.ts(2322)
}

といった形で、[key: T]: Uというシグネチャを記載することで、型チェックをすることが可能です。

オブジェクトにreadonly修飾子

let neonTetra: {
    readonly size: number
} = {
    size: 3
}

neonTetra.size = 5
// Cannot assign to 'size' because it is a read-only property.ts(2540)

まだ使ったことなかったですが、今後はどしどし使っていこうと思います。

Typeの合併型と交差型

type Fish = {
    canBranchialRespiration: boolean // えら呼吸
}
type Human = {
    canPulmonaryRespiration: boolean // 肺呼吸        
}

// 合併型
type FishOrHumanOrBoth = Fish | Human
let fishOrHumanOrBoth: FishOrHumanOrBoth = {
    canBranchialRespiration: true,
}

// 交差型
type FishAndHuman = Fish & Human
let fishAndHuman: FishAndHuman = {
    canBranchialRespiration: true,
    // Property 'canPulmonaryRespiration' is missing in type '{ canBranchialRespiration: true; }' but required in type 'Human'.ts(2322)
}

合併型は和集合、交差型は積集合という表現があったので、てっきり交差型は2つの型の両方が重複したプロパティしかTypeを引き継げないと思ったのですが、そんなことはなかったのが、一つ驚きでした。

ぬるぽについて

古いバージョンのTypeScrip、またはコンパイラの設定で、strictNullChecksオプションをfalseの場合は、null許容してしまうらしい。

オプションは明示的なので分かるが、昔のTypeScriptがそうだったとは・・
(いつのVersionまでそうだったのかは確認してません)

関数コンストラクタ

let swim = new Function('fishName', 'return fishName + " swims."')

こんな関数宣言の方法もあるらしい。
ただし、これだとパラメータと戻り値に型情報を書けないのでもちろん非推奨。

call, apply, bind

JavaScriptととしても、怪しかった分野だが、ようやくちょっと理解した。
それぞれ、第一引数で、thisをbindしてくれる。3つの違いは、第2引数以降の渡し方と、関数自体を呼び出すのか新しい関数を返してくれるか。

ジェネリックはいつバインドされるか?

// <T>を呼び出しシグネチャの一部として呼び出したので、具体的な型にbindするのは関数を呼び出すとき
type Filter = {
    <T>(array: T[], f: (item: T) => boolean): T[]
}

let filter: Filter = (array, f) => {
    let result = []
    for (let i = 0; i < array.length; i++) {
        if (f(array[i])) result.push(array[i])
    }
    return result
}

// ココでbind
filter([1, 2, 3], _ => _ > 2)
// 型エイリアスに<T>を設定すると、bindするのは、filter宣言時
type Filter<T> = {
    (array: T[], f: (item: T) => boolean): T[]
}

// ココでbind
let filter: Filter<number> = (array, f) => {
    let result = []
    for (let i = 0; i < array.length; i++) {
        if (f(array[i])) result.push(array[i])
    }
    return result
}

filter([1, 2, 3], _ => _ > 2)

はい、面白いです。

クラスは構造的に型付けされる

class Human {
    breath() {}
}

class Fish {
    breath() {}
}

function breath(animal: Human) {
    animal.breath()
}

let human = new Human
let fish = new Fish

human.breath()
fish.breath()
// エラーなし

C#, Javaなどではクラスは名前によって型付けされてので、人によっては驚きかと思います。私はエンジニア歴は浅いのでただただ「勉強になる」って感じ笑

ポリモーフィズム(多態性)

関数同様にクラスもインターフェースもジェネリック型パラメータをサポートしてるので、ポリモーフィズムが可能です。

5.8: ミックスイン

多重継承したいというモチベーションはわかるけど、コードは良くわからず飛ばした orz
また、一周したら戻ってくるかな。

5.9: デコレーター

これも、GoFのデザインパターンの本で学んでたので、概念はわかるけど、コードは良くわからず飛ばした orz

最後に

実際にアプリを作っていても、複雑な型エラーの場合に時間がかかることがあったが、多少は改善されると思う。

あと、インデックスシグネチャとか名前を色々知らなかったのや、そもそもtypeがaliasであったこととか、知らなかった挙動が知れて面白かった。

ここまでで、142/377ページ。残り235ページが楽しみながら読もう。

Discussion