JavaScriptを有効にしてください

プログラミングTypeScriptのメモ

 ·   ·  ☕ 12 分で読めます  ·  🐯 ブシトラ

そろそろ体系的に学ぶ必要のあるTypeScriptのメモ
ORELLYのTypeScriptを読むので、雑にメモしてく。
タイトルごとに区分するが、特に何もなければ書かない(タイトルのみ)

1章 イントロダクション

型安全性 → 型を使って、プログラムが不正なことをしないように防ぐこと

2章 TypeScript全体像

TypeScriptが独特なのは、バイトコードへと直接コンパイルする代わりに、なんと JavaScriptコードへとコンパイルする

TypeScriptコンパイラー(TSC)がよしなに抽象構文木(AST:abstract syntax tree)してくれる

大抵型推論があるので、明示的にアノテーションしなくてもOKな場合も多い。

コンパイル時にいチェックしてくれるのは神。
Jsだと実行時まで気づかない。。

3章 型について

型アノテーション(type annotation:明示的な型の指定)

anyはゴッドファーザー。
unknownは推論はしない。型を比較はできる。
もし、その特定の型としたことを想定したいのなら、typeof 等で分岐処理する必要がある

1
2
3
4
5
let a: unknown = 30
let b = a === 123
let c = a + 10
if (typeof a === 'number') {
let d = a + 10 // number }

明示的 → 実際に代入
推論 → 型に予測してもらう
基本的には推論でいい。

リテラル型 → ただ1つの値を表し、それ以外の値は受け入れない型
constを使うと再代入ができないのでリテラル型になる

1
2
3
4
5
6
let a = true // boolean
var b = false // boolean
const c = true // true
let d: boolean = true // boolean
let e: true = true // true
let f: true = false // エラー TS2322: 型 'false' を型 'true' に // 割り当てることはできません。

オブジェクトリテラル型とインデックスシグネチャについてはまた復習する

・オブジェクトリテラル({})は、オブジェクトの型を即席で定義するために用いられる。
・インデックスシグネチャ([key: T]: U)は、オブジェクトが、複数のkey(型T)を含む可能性があり値はU型になることを示している。

3.2.9 型alias

型エイリアスを使うかどうかを決めるときには、ある値に名前を付けて独立した変数にするかどうかを決めるときと同様の判断に従う

3.2.11 タプル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// [名前, 名字, 生まれ年]のタプル
let b: [string, string, number] = ['malcolm', 'gladwell', 1963]

// 鉄道運賃の配列。方向によって異なる場合があります
let trainFares: [number, number?][] = [
[3.75], [8.25, 7.70], [10.50]
]
// これは次のものと同等です
let moreTrainFares: ([number] | [number, number])[] = [
// ... ]

タプル型は不均一なリストを安全にコード化するだけでなく、それが型付けするリストの長さを限定する。これらの機能により、
従来のシンプルな配列よりもはるかに大きな安全が得られる

3.2.12 null undefined void never

undefinedは、あるものがまだ定義されていないことを意味し、nullは、値が欠如していることを意味します

意味
null 値の欠如
void return文を持たない関数の戻り値
undefined 値がまだ割り当てられていない変数
never 決して戻ることのない関数の戻り値

4章 関数

4-1 関数の宣言と呼び出し

JSの関数定義方法は5つある

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

function greet(name: string) {
  return 'hello' + name
}
// 名前付き関数


let greet2 = function(name: string) {
  return 'hello' + name
}
// 関数式

let greet3 = (name: string) => {
  return 'hello' + name
}
// アロー関数式

let greet4 = (name: string) => 'hello' + name
// アロー関数式省略

let greet5 = new Function('name', 'return "hello " + name')
// 関数コンストラクタ (使っちゃ駄目)

4.1.1 オプションパラメータとデフォルトパラメータ

オプションパラメータ → userId?
デフォルトパラメータ → userId = “not assigned in”

デフォルトパラメータを使うほうがよさげ

4.1.2 レストパラメータ

可変長引数のこと

…numbers みたいなやつ

4.1.3 call apply bind

呼び出し方のやつ。さらっとしか見てない。
あんま使うイメージない

4.1.4 thisの型付け

関数でthisを使う場合は、期待するthisの型を、最初のパラメータとして宣言すればいいっぽい

4.1.5 ジェネレーター

https://typescript-jp.gitbook.io/deep-dive/future-javascript/generators

遅延評価?
あまり使ったことなかった

4.1.6 イテレーター

イテレーターはジェネレータの裏返し。ジェネレーターが一連の値を生成するのに対し、イテレーターは
それを利用する。

1
2
3
4
5
6
7
let numbers = {
  *[Symbol.iterator]() {
    for (let n = 1; n <= 10; n++) {
      yield n;
    }
  },
};

わからん。。

4.1.7 呼び出しシグネチャ

1
2
3
4
5
6
7
type Log = (message: string, userId?: string) => void
let log: Log = (
  message,
  userId = 'Not signed in'
) => {
  let time = new Date().toISOString()
   console.log(time, message, userId) }

4.1.8 文脈的型付

4.1.9 オーバーロードされた関数の型

後で読む

4.2 ポリモーフィズム

ジェネリクス型の話。
ここむずいんよなあぁ。
【TypeScript】Generics(ジェネリックス)を理解する

↑上記記事わかりやすい。
要するに、関数の中でおなじ型が入るなら、 として とできるイメージか。

4.2.3 ジェネリックの型推論

4.2.4 ジェネリック型エイリアス

4.2.5 制限付きポリモーフィズム

4.2.5.1 複数の制約を持つ制限付きポリモーフィズム

ジェネリクスの部分は後で書く。

4.3 型駆動開発

まず型シグネチャで概略を記述し、その後で値を埋め込むプログラミングのスタイル

1
function map<T, U>(array: T[], f: (item: T) => U): U[]

これまでmapを見たことがなかったとしてもmapが何をするかについてある程度は直観的にわかるはずです。つまり、Tの配列と、Tから Uへとマッピングする関数を取り、Uの配列を返すということです。それを知るために、その関数の実装を見る必要はありません!

↑ 直感的にまだ分かるようにはなっていないが、型の重要さは分かる。

5章 クラスとインターフェイス

チェスを例にオブジェクト指向

publicとprivateはRubyにもあるので省略。
protected → このクラスとサブクラスのインスタンスからアクセス可能。

5.2 super

5.3 戻り値の型としてthis を使用する

5.4 インターフェイス

typeinterface の違いは、

1.型エイリアス(type)のほうが、右辺に任意の型を指定できるという点で、より汎用的である点

1
2
type A = number
type B = A | string

上記をインターフェイス型に変換することはできない

2.インターフェースを拡張する場合に、TypeScriptは、拡張元のインターフェースが拡張先のインターフェースに割り当て可能かどうかを確認する点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
interface A {
  good(x: number): string
  bad(x: number): string
}

interface B extends A {
  good(x: string | number): string
  bad(x: string): string
}
// Aは型エイリアスなので、型インターフェイスに拡張はできない

3.同じスコープ内に同じ名前のインターフェースが複数存在する場合、それらは自動的にマージされる点
→ 宣言のマージ という

5.4.1 宣言のマージ

interfaceとtypeの違い、そして何を使うべきかについて
↑ だと インターフェイス型を基本使うように公式も言っているらしい。現場によって異なるのか確認はしたほうがよさそう

5.4.2 実装

クラスを宣言するときに implementsキーワードを使うと、そのクラスが特定のインターフェースを
満たしていることを表現できる

5.4.3 「インターフェースの実装」対「抽象クラスの拡張」

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

5.6 クラスは値と型の両方を宣言する

TypeScript で表現できることの多くは、値か型の「どちらか」

5.7 ポリモーフィズム

復習する

5.8 ミックスイン

● 状態(すなわち、インスタンスプロパティ)を持つことができます。
● 具象メソッド(抽象メソッドでないもの)だけを提供できます。
● コンストラクターを持つことができます。コンストラクターは、クラスがミックスされた順序と同じ順序で呼び出されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type ClassConstructor<T> = new (...args: number[] | string[]) => T;
function withEZDebug<C extends ClassConstructor<{ getDebugValue(): object }>>(Class: C) {
  return class extends Class {
    debug() {
      let Name = this.constructor.name
      let value = this.getDebugValue()
      return Name + '(' + JSON.stringify(value) + ')'
    }
  }
}

class HardToDebugUser {
  constructor(
    private id: number,
    private firstName: string,
    private lastName: string
  ) {}
  getDebugValue() {
    return {
      id: this.id,
      name: this.firstName + ' ' + this.lastName
    }
  }
}

let User = withEZDebug(HardToDebugUser)
let user = new User(3, 'Emma', 'Gluzman')
console.log(user.debug());

↑ Userはデバッグ出来るクラスを拡張したクラスを持っているからデバッグできる。むずいなここ。。

5.9 デコレーター

デコレーターはまだ実験的なものらしい。(2022/03)
TypeScript のデコレーターがより成熟した機能になるまで、その使用は避け、代わりに通常の関数を 使用することをお勧めします。

って書いてあったのでスルー

5.10 final クラスをシュミレートする

finalとは、クラスを拡張不可と指定したり、メソッドをオーバーライド不可と指定したりするために、いくつかの言語で使われるキーワード
クラスを直接インスタンス化することも防止します。しかし、finalクラスに対して 私たちが望むのは、それを拡張できなくすることだけで、インスタンス化の機能は残しておく必要がある時につかう

5.11 デザインパターン

ファクトリー(Factory)パターンは、何らかの型のオブジェクトを作成するための方法で、どのよう
な具体的なオブジェクトを作成すべきかの決定を、そのオブジェクトを作成する特定のファクトリー(工場の意)に任せます

ビルダー(Builder)パターンは、オブジェクトの構築と、そのオブジェクトを実際に実装する方法と
を分離するためのもの

コードは省略。

6章 高度な型

6.1 型の間の関係

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

サブタイプとは? → BがAのサブタイプなら、BはAの要求される型を安全に使える

6.1.2 変性

パラメーター化された型(ジェネリック型)や複雑な型は、複雑になりがち

6.1.2.1 形状の配列と変性

不変性(invariance) Tそのものを必要とする。
共変性(covariance) <:Tであるものを必要とする。
反変性(contravariance) >:Tであるものを必要とする。
双変性(bivariance) <:Tまたは >:Tであれば OK。

6.1.2.2 関数の変性

Crow < Bird < Animal で、変性を説明しているがテストコードがうまく動かず。。

6.1.3 割当可能性

  1. A <:Bである。
  2. A が anyである。

であれば割当可能

6.1.4 型の拡大

ミュータブルなら拡大され
イミュータブルなら拡大されない

nullまたは undefinedに初期化された変数は、anyに拡大される

6.1.4.1 constアサーション

TypeScript には特別なconstアサーションが用意されており、これを使うと、宣言と同時に型の拡大を抑えることができます。

6.1.4.2 過剰プロパティチェック

6.1.5 型の絞り込み

6.2 完全性

1
2
3
4
5
6
type Weekday = 'Mon' | 'Tue'| 'Wed' | 'Thu' | 'Fri'
type Day = Weekday | 'Sat' | 'Sun'

function getNextDay(w: Weekday): Day { switch (w) {
 case 'Mon': return 'Tue' }
}

上記を解決するのは Day | undefined にするか、 全部 case文で書くか

6.3 高度なオブジェクト型

ルックアップ方式。← 定義済みの型[“キー”]と書くことで、指定したキーに対応する型を取り出すことができる

6.3.1.2 keyof 演算子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type APIResponse = {
    user: {
      userId: string
      friendList: {
        count: number
        friends: {
          firstName: string
          lastName: string
        }[]
      }
   }
}

type ResponseKeys = keyof APIResponse // 'user'
type UserKeys = keyof APIResponse['user'] // 'userId' | 'friendList'
type FriendListKeys = keyof APIResponse['user']['friendList'] // 'count' | 'friends'

上記の keyofを使うと、こんな感じにできる

1
2
3
4
5
6
7
8
9
function get<
  O extends object,
  K extends keyof O
>(
  o: O,
  k: K
): O[K] {
 return o[k]
}

oの型が {a: number, b: string, c: boolean} である場合
keyof O は a/b/c K はnumber/string/boolean になる。
ので、型が違うとエラーになる

6.3.2 レコード型

入れ子になっているものをすっきり書くやつ
Record<K,T>
【TypeScript】「Record」型について

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface Person {
  name: string;
  age: number;
}

type members = "one" | "two" | "three" | "four";

const member:Record<members, Person> = {
  one: { name: "taro", age: 12 },
  two: { name: "hanako", age: 15 },
  three: { name: "saburo", age: 18 },
  four: { name: "siro", age: 17 },
};

// const member:{
//   one:Person,
//   two:Person,
//   three:Person,
//   four:Person,
// } = {
//   one: { name:'taro',age:12 },
//   two: { name:'hanako',age:15 },
//   three: { name:'saburo',age:18 },
//   four: { name:'siro',age:17 },
// }

// ↑ 本来だったら、 type membersの 数値をこう書く。冗長

6.3.3 マップ型

後で復習

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type WeightUnit = 'kg' | 'g'

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

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

利用する側では、

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { Fish } from './Fish'

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

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

unit と value を importして指定出来る感じ。便利。

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

6.4 関数にまつわる高度な型

1
2
3
4
5
6
7
8
9
function tuple<T extends unknown[]>(
  ...ts: T
): T {
  return ts
}

tuple(["aaa"])

console.log(tuple(["aaa"], [true],[1]))

タプルで、可変長引数で表すとき上記のように書く。
すげえ。でも配列で可変長引数で何でもOKにするパターンを想定してコードを書くのってどうなの?
柔軟なんだよっていうことだけ頭にいれておこう

6.4.2 ユーザー定義型ガード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

function isString(a: unknown): a is string {
  return typeof a === "string";
}
//  function isString(a: unknown): boolean だと駄目

function parseInput(input: string | number) {
  let formattedInput: string
  if (isString(input)) {
    formattedInput = input.toUpperCase();
  }
}

6.5 条件型

1
2
3
4
5

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

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

型レベルの三項演算子。。最もユニークとかかれていたがイマイチぴんとこな。

6.5.1 分配条件型

型定義に条件分岐を持ち込むことができる。

T extends U ? A : B

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

type ToArray2<T> = T extends unknown ? T[] : T[]
type A = ToArray2<number> // number[]
type B = ToArray2<number | string> // number[] | string[]

type Without<T, U> = T extends U ? never : T;

type C = Without<
boolean | number | string, boolean
> // number | string
// Tには含まれているがUには含まれていない型を計算する

型の条件分岐。頭の片隅にいれておこう

TypeScript の条件型(Conditional Type)と infer キーワード

6.5.2 inder キーワード

inferはその条件分岐で推論された型を指すときに用いることができます。

ジェネリック型を関数でいうところの引数(props)と呼ぶならば、
inferは引数によって動的に値が変化する変数のようなもので、infer Uと記述したら、U型を型情報に含めることができます。

【TypeScript】 inferに詳しくなろう

6.5.3 組み込みの条件型

1
2
3
4
5
Exclude<T, U> // Tに含まれているが Uには含まれていない型を計算します。
Extract<T, U> // Tに含まれている型のうち、Uに割り当てることのできるものを計算します。
NonNullable<T> // nullと undefinedを除外した Tのバージョンを計算します。
ReturnType<F> // 関数の戻り値の型を計算します
InstanceType<C> // クラスコンストラクターのインスタンス型を計算します。

6.6 エスケープハッチ

省略

6.7 名前的型をシュミレートする

同じ string 様々な Idを定義していた時の引数を、Id別に関数化して代入できないようにするイメージ。

6.8 プロトタイプを安全に拡張する

省略

6.9 まとめ

すべてのものを理解できていなかったり、覚えていなかったりしても、大丈夫です。何かをより安全 に表現する方法に苦労して取り組んでいるときに、この章に戻り、リファレンスとして活用してください。

この一行でだいぶ安心する。笑

共有

busitora
著者
ブシトラ
エンジニア