プログラミングTypeScriptの読書メモ - 型

難しすぎて、一回3−4ページ文しか進められてない、、

よく自分に聞く:Is this something about TYPEScript(exists only during compile time) or not(to be dealt at runtime)?

聞きながら勉強すると大変助かります。

高度な型

サブタイプとスーパータイプ

  • anyはすべての型のスーパータイプ
  • neverはすべての型のサブタイプ

変性

  • 不変性(invariance)
  • 共変性(covariance)
  • 反変性(contravariance)
  • 双変性(bivariance)

ディフォルトでTypescriptの型に関して共変です。 {"strictFunctionTypes": true}の場合、関数型にはそのパラメータの型が反変にと扱うようにする。具体は下記を参照ください。

関数型の関係と反変

class Animal {}
class Bird extends Animal {
    chirp() {}
}
class Crow extends Bird {
    caw() {}
}

function clone (f: (b:Bird) => Bird): void {

}

clone関数は、関数型のパラメータを期待する。(b: Bird) => Bird型の関数、とそのサブタイプの関数を渡すことができる。 ここまでは普通の共変であり、反変に関係ない。

ではどんな関数は(b: Bird) => Birdのサブタイプでしょうか

// これをベースとして考えてみる
function bToB(b: Bird) : Bird {
    return new Bird();
};

function bToC(b: Bird) : Crow {
    return new Crow();
}

function bToA(b:Bird) : Animal{
    return new Animal()
}

function aToB(a:Animal) : Bird {
    return new Bird();
}

function cToB(c: Crow) : Bird {
    return new Bird()
}

clone(bToB) // OK

clone(bToC) // OK

clone(bToA) // Error 2345

clone(aToB) // OK

clone(cToB) // Error 2345

(b: Bird) : Crow(b: Bird) => Birdのサブタイプであり、(b:Bird) : Animalはサブタイプではない。この戻り値の振舞いはまだ共変です。(戻り値はサブタイプの関係 → 関数はサブタイプの関係)

(a:Animal) : Bird(b: Bird) => Birdのサブタイプであり、(c: Crow) : Birdはサブタイプではない。このパラメータ部分の振舞いは反変です。(パラメータはスーパータイプの関係 → 関数はサブタイプの関係)

過剰プロパティチェック(excess property checking)

6.1.4.2

タグ付き合併型

TODO: 本の例が理解できてない

typeofで型を絞り込んでも、型が十分に推論できない場合がある

下記の推論はなぜ??

type Cat = { id: number, purrs: boolean, eat: boolean }
type Dog = { id: string, bark: boolean, eat: () => void }
type Pet = Cat | Dog

function playWith(pet: Pet) {
    if (typeof pet.id == "number") {
        pet.eat // (property) eat: boolean | (() => void)
        return
    }
    pet.eat // (property) eat: boolean | (() => void)
}

本によると、「合併型のメンバーは型が重複する可能性があるので」、Unionのどちらは推論できない。 具体の場合は思いつかないT.T. TODO

一意であるリテラル型を使ってタグ付けをすると、その分岐を特定できる

type Cat = { type: 'nyan', id: number, purrs: boolean, eat: boolean }
type Dog = { type: 'wan', id: string, bark: boolean, eat: () => void }
type Pet = Cat | Dog

function playWith(pet: Pet) {
    if (pet.type == "nyan") {
        pet.eat // (property) eat: boolean
        return
    }
    pet.eat // (property) eat: (() => void)
}

完全性(noImplicitReturns)

noImplicitReturnsオプションはstrictフラグに含まれないため、有効したい場合は明示的に指定する必要がある

ルックアップ型

type User = {
    name: string,
    friendList: {
        count: number,
        friends: {
            name: string
        }[]
    }
}

type FriendList = User['friendList']
type FriendInfo = User['friendList']['friends'][number] // note this!

keyof演算子

type keys = keyof {a: string, b: number, c: boolean} // type keys = "a" | "b" | "c"

レコード(Record)型

One of Utility Types. → Map型

let r: Record<'a' | 'b' | 'c', string> = { // Error: Property 'c' is missing in type '{ a: string; b: string; }' but required in type 'Record<"a" | "b" | "c", string>'.
    a: "",
    b: ""
}

Map型

Utility Types.

汎用なユーティリティ機能です。強い! マイナス演算子-で省略可能、readonlyなど制約の撤去ができる

type OptionalUser = {
    [K in keyof User]?: User[K]
}

// ! we can remove optional constraint
type RequiredUser = {
    [K in keyof OptionalUser]-?: User[K]
}

type NullableUser = {
    [K in keyof User]: User[K] | null
}
type ReadonlyUser = {
    readonly [K in keyof User]: User[K]
}

// ! we can remove readonly constraint
type ModifiableUser = {
    -readonly [K in keyof User]: User[K]
}

関数にまつわる型

Tupleの型推論

マジックだこれ。

const a = [1, true] // a is (number | boolean)[]

function tuple<T extends unknown[]>(...t: T): T {
    return t
}

const b = tuple(1, true) // b is [number, boolean]

型ガイド

面白い

// this works at runtime so TypeScript do not know the type at compile type
function isString(p: unknown): boolean {
    return typeof p == 'string'
}

// now TypeScript knows the type is string when this function return true
function isStringType(p: unknown): p is string {
    return typeof p == 'string'
}

条件型

type x = string
type A = x extends string ? true : false // type of A is true
type B = x extends number ? true : false // type of B is false

分配法則

本の例Without<T, U>について、分配法則がなかったとする場合の推論が間違ったようだ。 『プログラミングTypeScript』の正誤表

// type of C is boolean | number | string, which is T.
type T = (boolean | number | string)
type C =  T extends boolean ? T : never

// type of E is boolean !!
type Select<T, U> = T extends U ? T : never
type E = Select<boolean | number | string, boolean>
// because it is the same as Select<boolean> | Select<number> | Select<string>

inferキーワードで型変数を宣言する

type ElementType<T> = T extends (infer U)[] ? U : T

明確な割立てアサーション


let userId: string

function fetchUser() {
    userId = 'abc'
}

fetchUser()

userId.toUpperCase(); // Error: Variable 'userId' is used before being assigned.

To fix this,

let userId!: string
// OR
userId!.toUpperCase()

名前的型

TypeScriptの型は構造的なので、型のブランド化というテクニックで名前的な型を作る

https://qiita.com/suin/items/ae9ed911ebab48c98835 https://qiita.com/suin/items/57cfc0ec9bb1a6995aa5 https://typescript-jp.gitbook.io/deep-dive/main-1/nominaltyping

comments powered by Disqus