#
ドキュメント

Document

自分のための備忘録です。

TypeScript

「プロを目指す人のためのTypeScript入門」の読書メモです。

TypeScript とは

TypeScript は JavaScript静的型付け を追加した言語です。

開発環境

  1. Node.js をインストール
    • TypeScript コンパイラは Node.js を使用
  2. TypeScript をインストール

[!NOTE] コード内で Node.js を使用する場合( require など)は @type/node もインストールします。 逆を言えばコード内で Node.js を使用しない場合は @type/node は必要ありません。

TSC(TypeScript Compiler)

  1. 型チェック
  2. トランスパイル(コンパイルとも言える)

どのバージョンの JavaScript ( ECMAScript )[^1] にトランスパイルするのかを ts.config の target プロパティで指定します。

[^1]: 例) es5, es2015, es2020 など。

[!NOTE] tsc に個別ファイルを指定した場合は ts.config を無視します(例: $ npx tsc /path/to/file.ts )。

開発の流れ

  1. TypeScript から JavaScript にトランスパイル( tsc )
  2. 1. でトランスパイルした JavaScript を Node.js で実行( node )

型推論

型推論の基本は式から型を推論します。

// 式 10 は number なので num の型は number (厳密には数値 `10` のリテラル型)
const num = 10;
// 式 test は string なので s の型は string(厳密には文字列 `test` のリテラル型
const s = 'test';

から関数式の内部(ここでは引数型)を推論(逆方向の型推論と呼ぶ)できる場合は型アノテーションを省略できます。
逆方向の型推論を contextual typing と呼びます。

type F = (value: string) => string;

// 引数の型アノテーションを省略する場合
const getName: F = (name) => return name;

// 引数の型アノテーションを省略しない場合
const getName: F = (name: string): string => return name;

JavaScript の値と対応する型(基本型)

TypeScript プログラムにおける値は、プリミティブと、あとに説明するオブジェクトの2種類に大別されます。 出典:プロを目指す人のためのTypeScript入門 p34

※ 関数も関数オブジェクトと呼ばれることからわかるようにオブジェクトです。

  • オブジェクト型
  • 関数型[^function_type]
  • プリミティブ型
    • 文字列型
    • 数値型
    • 真偽値型
    • BigInt型
    • null型: null 型の値は null のみ
    • undefined型: undefined 型の値は undefined のみ[^undefined]
    • シンボル型

[^undefined]: JavaScript で存在しない プロパティ にアクセスした場合は undefined を返します。一方存在しない オブジェクト にアクセスした場合はランタイムエラーになります。 [^function_type]: 関数もオブジェクトですが、型は function なのでリストを分けています。

文( sentence ) と式( expression )

はセミコロンで区切られて上から順に実行されます[^sentence]。

宣言( declaration )とその他に分けられます。
例) 例えば(正式には) 変数宣言文とは呼ばす 変数宣言 、関数宣言文とは呼ばず 関数宣言 です。

[^sentence]: 制御構文、関数宣言( function Foo() {} )の巻き上げ、type 文のコンパイル時チェックなどの例外があります。また行末のセミコロンは省略可能です。

  • 式は結果がある
  • 式(及び後述する式文)で文を構成する

式は一般に何らかの計算を表します。そして、その計算の結果が式の結果となるのです。

出典:プロを目指す人のためのTypeScript入門 p27

式文

式文は、式のあとにセミコロンを書く文です。

文と式の違い

文と式の決定的な違いは何でしょうか。それは結果があるかどうかです

出典:プロを目指す人のためのTypeScript入門 p27

サンプル

/*
 * 文の例
 */
// 文1 変数宣言
const hello = 'Hello';
// 文2 関数宣言
function getHello(name: string): string {
    return `Hello ${name}!`;
}
function loggingHello(name: string): void {
    console.log(`Hello ${name}!`);
}

/*
 * 式
 */
// 例1
'Hello'
// 例2
3 + 5 // 式 + 式
// 例3(変数名も式であることに注意)
foo

/*
 * 式文
 */
// 式文1 代入
message = 'Hello World'; // 代入演算子の左辺にある変数名( message )も式になる
// 式文2 戻り値のない関数呼び出し
loggingHello('Hiroshi');

ブロック

ブロックとは、{ }の中に文をいくつでも書けるとう構文で、ブロック自体は1つの文として扱われます。ブロックを文として実行する場合、その中に書かれた文が上から順番に実行されます。

出典:プロを目指す人のためのTypeScript入門 p63

ブロックが文の一種であるということは、if (条件式) 文という構文の文のところにブロックを置くことができるということです。

出典:プロを目指す人のためのTypeScript入門 p63

ブロックの使い道

  • 複数の文を1つの文にまとめる 出典:プロを目指す人のためのTypeScript入門 p63

  • ブロックスコープの導入 出典:プロを目指す人のためのTypeScript入門 p63

リテラル

リテラルとは、何らかの値を生み出すための式のことで、生み出したい値に応じていくつかの種類があります。

出典:プロを目指す人のためのTypeScript入門 p35

つまり リテラル は TypeScript の を生み出す式。 TypeScript の は前述のとおり オブジェクトプリミティブ に大別されます。

  • 文字リテラル
  • 数値リテラル
  • オブジェクトリテラル
  • ....

注意すべき等価演算子

== と null

x == null は x が nullundefined のときのみ true になります。 null が良くて undefined は駄目な場合は x === null を使います 。

=== NaN

x === NaN は x がどのような値でも( NaN であっても)必ず false になります。 NaN を判定したいときは Number.isNaN() を使用します。

?? ( null 合体演算子)

x ?? y は x が null または undefined のときのみ y を返します。

出典:プロを目指す人のためのTypeScript入門 p56

x || y は x が 0 や 空文字や false の場合も y を返します。

オブジェクト

部分型関係( subtypig relation )

簡潔にまとめると部分型は親の代替ができる型のことを表す。

型Sと型Tがオブジェクト型だとして、次の2つの条件が満たされればSがTの部分型になります。

  1. Tが持つプロパティはすべてSにも存在する。
  2. 要件1の各プロパティについて、Sにおけるそのプロパティの型はTにおけるプロパティの方の部分型(または同じ型)である。

出典;プロを目指す人のためのTypeScript入門 p97

type[^interface] 文 で型に別名を付ける

type 文は オブジェクト型 だけでなく プリミティブな型別名型名)を作成できます。

type文は決して「新たに型を作って利用可能にする」ものではなく、「すでにある型に別名をつける」だけのもの

出典:プロを目指す人のためのTypeScript入門 p89

// オブジェクト型
type MyObject = {
  name: string;
  age: string;
};
// string 型
type MyString = string;
// 型チェックでエラー
const myString: MyString = 28; // Type 'number' is not assignable to type 'string'.
// ※ JavaScript は変数の大文字・小文字を区別します。かつ型名、変数名は別の名前空間です(なので 重複する名前の型名と変数名が共存できます)。

type 文による型宣言はその型名の使用より後でも問題ありません(型チェックはコンパイル時に実行されるためです)。

[^interface]:interface 宣言 で作成できる型名は オブジェクト型 のみです。多くのの場合は type 文 で代用可能です。

型引数( type parameters )

型引数は、型を定義するときにパラメータをもたせることができるというもので、ジェネリクスに少し似ています。
出典:プロを目指す人のためのTypeScript入門 p100

ジェネリクス(generics)とは、型引数を受け取る関数を作る機能のことです。
出典:プロを目指す人のためのTypeScript入門 p168

type Family<Parent, Child> = {
    mother: Parent;
    father: Parent;
    child: Child;
};
const family: Family<number, string> = {
    mother: 0,
    father: 100,
    child: "1000"
};

type OtherFamily = Family<string, string>;
const otherFamily: OtherFamily = {
    mother: "0",
    father: "100",
    child: "1000"
}

適切な数の型引数を指定せずに型を使用した場合はコンパイルエラーという結果になります。 出典:プロを目指す人のためのTypeScript入門 p101

// エラー: Generic type 'Family' requires 2 type argument(s).
const family: Family = {
    mother: 0,
    father: 100,
    child: "1000",
}

部分型関係による型引数の制約

type 文において型引数を宣言するとき、extends という構文を使うことができます。具体的には、型引数の宣言の後ろに extends 型を付加することができます。この構文は、「この型引数は常に型の部分型でなければならない」という制約(constraint)を意味します。 出典:プロを目指す人のためのTypeScript入門 p101

type HasName = {
    name: string;
};

type Family<Parent extends HasName, Child extends HasName> = {
    mothier: Parent;
    father: Parent;
    child: Child;
};

for-of 文とイテレータ

TypeScriptにはイテレータ(Iterator)という概念が存在します。 ... イテレータは繰り返し処理のための汎用的なインターフェースです。イテレータを通じて繰り返し処理ができる値のことをJavaScript用語でIterableと呼びます。for-of文のofの右の式には、厳密には配列だけでなくそれ以外のIterableな値を与えることができまるのです。 出典:プロを目指す人のためのTypeScript入門 p111)

イテレータープロトコル

より具体的に言うと、イテレーターは、次の 2 つのプロパティを持つオブジェクトを返す next() メソッドを持つことによってイテレータープロトコルを実装するオブジェクトです。

value
反復シーケンスの次の値.

done
シーケンスの最後の値が既に消費されている場合に true となります。done と並んで value が存在する場合、それがイテレーターの戻り値となります。

出典:イテレーターとジェネレーター
参考:Generator

イテレーターの例(ジェネレーター)

/*
 * ジェネレーターはイテレータープロトコルを実装している
 */
function* createIterator() {
    const limit = 10;
    for (let i = 0; i < limit; i++ ) {
      yield i;
    }
}

const iterator = createIterator();

for (const value of iterator) {
    console.log(value);
}

/*
 * ジェネレーターはイテレータープロトコルを実装しているので `next()` を持っており以下のようにも書けます。
 */
const otherIterator = createIterator();
while(true) {
  const result = otherIterator.next();
  if ( result.done ) { 
    break;
  }
  console.log(result.value)
}

スプレッド構文( Spread syntax )

const one = {
  name: 'John',
  age: 30,
}

const other = {
  hobby: 'Reading',
  ...one,
}

console.log(other);
// {
//   "hobby": "Reading",
//   "name": "John",
//   "age": 30
// }

// typeof キーワードで動的に型名作成
type Other = typeof other;
// type Other = {
//    name: string;
//    age: number;
//    hobby: string;
// }

typeof

typeof には typeof キーワードと typeof 演算子の 2 種類あります。混同しないように注意が必要です。

  • typeof キーワード: 型を作るキーワード type 文 で使用
  • typeof 演算子[^typeof]

[^typeof]: typeof null が object になることに注意してください(歴史的経緯)。

分割代入( Destructuring assignment )

オブジェクトのプロパティの中身をプロパティと同名の変数に入れたい場合にしかこのパターンを用いることができません。

出典:プロを目指す人のためのTypeScript入門 p114

const rect = {width: 100, height: 200, depth: 300};
const {width, height, depth} = rect;
console.log(width, height, depth);
// 100, 200, 300

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

分割を使用して構造体の深いデータを取得

出典: https://typescript-jp.gitbook.io/deep-dive/future-javascript/destructuring

const page = {
    title: 'Hello World',
    content: {
        catchCopy: 'HELLO WORLD',
        lead: 'Welcome to my page.',
        body: 'This is content'
    }
}

var {content: {catchCopy}} = page;
console.log(catchCopy);
// HELLO World
console.log(content);
// ERR content is not defined
var { content } = page;
console.log(content);

//
{
  body: "This is content",
  catchCopy: "HELLO WORLD",
  lead: "Welcome to my page."
}

プロパティ名に別の変数名を使う

contentfoo に代入します。

const page = {
    title: 'Hello World',
    content: {
        catchCopy: 'HELLO WORLD',
        lead: 'Welcome to my page.',
        body: 'This is content'
    }
}
const {content: foo} = page;
console.log(foo);

// {
//  "catchCopy": "HELLO WORLD",
//  "lead": "Welcome to my page.",
//  "body": "This is content"
// } 

スプレッド構文や分割代入を使ったオブジェクトのコピー

スプレッド構文

const one = {
    foo: 'a',
    bar: 'b',
    baz: 'c',
};
const other = {...one};

console.log(other);
// {
//   "foo": "a",
//   "bar": "b",
//   "baz": "c"
// }

one.foo = 'x';
console.log(other.foo); // a

分割構文

const one = {
    foo: 'a',
    bar: 'b',
    baz: 'c',
};
const {...other} = one;

console.log(other);
// {
//   "foo": "a",
//   "bar": "b",
//   "baz": "c"
// }

one.foo = 'x';
console.log(other.foo); // a

関数

TypeScript は return しない関数の戻り値を undefined とみなします。。

オプショナルな引数

オプショナルな引数の型は undefine とのユニオン型になります。

// オプショナルな引数の型は undefine とのユニオン型になる
// (parameter) a: string | undefined
function foo(a?: string){
    // 呼び出しで引数が省略されたときの a は undefined
    if (a) {
        console.log(a)
    } else {
        console.log('省略')
    }
}

foo('test');
// test
foo();
// 省略

/*
 * オプショナルではない引数が省略されたらコンパイルエラー
 */
function bar(a: string){
    // 呼び出しで引数が省略されたときの a は undefined
    if (a) {
        console.log(a)
    } else {
        console.log('省略')
    }
}

bar();
// Expected 1 arguments, but got 0.

関数型の型名

関数型宣言: type 型名 = (引数名: 型名) => 戻り値型

type User = {
  name: string;
  age: number;
}

// 関数型の型を明記
type GetNameFunc = (user: User) => string;

const getName: GetNameFunc = (user: User): string => {
  return user.name;
}

console.log(getName({ name: 'John', age: 30 })); // John

/*
 * コンパイル時の型チェックでエラー
 * // 関数の型チェックエラー文
 * > Type '(name: string) => string' is not assignable to type 'GetNameFunc'.Types of parameters 'name' and 'user' are incompatible.
 *
 * // 呼び出し時の型チェックのエラー文
 * > Type 'User' is not assignable to type 'string'. const getNameFaulty: GetNameFunc = (name: string):string => {
 *   return name;
 * }
 */
console.log(getNameFaulty('jhon')) // 文字型を渡しているので型チェックエラー

ジェネリクス

ジェネリクス(generics)とは型引数を受け取る関数を作る機能のことです。

出典:プロを目指す人のためのTypeScript入門 p168

// 出典:プロを目指す人のためのTypeScript入門 p169
function repeat <T>(elem: T, num: number): T[] {
  const result: T[] = [];
  for (let i = 0; i < num; i++ ) {
    result.push(elem)
  }
  return result;
}

console.log(repeat('a', 5)); // ["a", "a", "a", "a", "a"]
console.log(repeat(123, 3)); // [123, 123, 123]
// 関数の型名をジェネリクスで定義
type Echo<T> = (value: T) => T;

const echoString: Echo<string> = (str: string) => {
   return str;
}
const echoNumber: Echo<number> = (num: number) => {
   return num;
}

console.log(echoString('Hello')); // Hello
console.log(echoNumber(123)); // 123

以下のように簡潔にかけます。

const echo = <T>(value:T): T => {
  return value;
}

console.log(echo('Hello'));
console.log(echo(123));

todo
結果を表示する

コールバック関数

type MyCallback<T> = (value: T[]) => T[];

const caller = <T>(callback: MyCallback<T>, args: T[]): T[] => {
  return callback(args);
}

const result = caller((nums: number[]) => {
  return nums.map(num => num * 2);
}, [1, 2, 3]);

console.log(result); // 2 4 6

関数の部分型関係

親と代替できることが重要です。
より少ない入力(引数)からより多い出力(戻り値)を返します。

出典:プロを目指す人のためのTypeScript入門 p165

引数の型アノテーションを省略可能な場合

から関数式の内部(ここでは引数型)を推論(逆方向の型推論と呼ぶ)できる場合は型アノテーションを省略できます。
逆方向の型推論を contextual typing と呼びます。

type F = (value: string) => string;

// 引数の型アノテーションを省略する場合
const getName: F = (name) => return name;

// 引数の型アノテーションを省略しない場合
const getName: F = (name: string): string => return name;

クラス

クラス宣言の重要な特徴のひとつは、クラスオブジェクトという値を作るものであると同時に、インスタンスの型を宣言するものであるということです。

出典:プロを目指す人のためのTypeScript入門 p201

変数名と型名の名前空間

名前には2種類あることを理解する必要があります。すなわち、変数名と型名です。これらは別々の名前空間に属しています。

出典:プロを目指す人のためのTypeScript入門 p203

instanceof 演算子

class User {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const user1 = new User('user1', 18);

// user1 は User のインスタンス
console.log(user1 instanceof User) // true

// class User { } で User 型が定義されていることに注意
const user2: User = {
    name: 'user2',
    age: 19
}

// user2 は User 型として扱えるが User のインスタンスではない
// {} は new Object の糖衣構文なので Object もインスタンスになる
console.log(user2 instanceof User) // false
todo console.log(user2 instanceof Object) // true

// typeof 演算子は object
console.log(typeof user1, typeof user2); // "object", "object"

クラス宣言とプロトタイプ

クラスについて MDN に以下のように記載されています。

JS のクラスはプロトタイプに基づいて構築されていますが、一部の構文や意味はクラスに固有です。

出典:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes

「一部の構文や意味はクラスに固有です」との記載があるのでクラスが ES 5 のプロトタイプベースの糖衣構文と言い切れるかはわかりません。

class User {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}
/*
 * TypeScript は JavaScipt のスーパセットなので prototype 継承も有効
 */
// new User() でインスタン( user1 )を作成
const user1 = new User('Jhon', 25);
// インスタンス( user1 )はコンストラクタ( User )の prototype へのリンクを持つ
console.log(User.prototype === Object.getPrototypeOf(user1)); // true
console.log(user1.constructor === User); // true

const user2: User = {
  name: 'Jhon',
  age: 25
}
// user2 は( User 型のオブジェクトだが)User のインスタンスではない
// ↑より User.prototype へのリンクは持たない
console.log(User.prototype === Object.getPrototypeOf(user2)); // false
console.log(user2.constructor === User); // false
// user2 は Object のインスタンス(オブジェクトリテラル {} は new Object の糖衣構文)
// ↑より user2 は Object.protype へのリンクを持つ
console.log(Object.prototype === Object.getPrototypeOf(user2)); // true
console.log(user2.constructor === Object); // true

クラスのメソッドは protoype のメソッドになる。

class Product {
    #name: string;
    #price: number;

    constructor(name: string, price: number) {
        this.#name = name;
        this.#price = price;
    }

    getOrder(num: number): string {
        return `${this.#name} is ${this.#price * num}`
    }
}


const product1 = new Product('product1', 100);
const product2 = new Product('product2', 200);

console.log(typeof product1.getOrder, typeof product2.getOrder); // function, function
// どちらも同じメソッド( Product.prototype.getOrder )を参照
console.log(product1.getOrder === product2.getOrder); // true
console.log(product1.getOrder === Product.prototype.getOrder); // true
console.log(product2.getOrder === Product.prototype.getOrder); // true

継承

また、コンストラクタもオーバーライドすることができます。ただし、その場合は子クラスのコンストラクタの引数の中にsuper呼び出しを含める必要があります。

プロを目指す人のためのTypeScript入門 p212

メソッドと prototype

クラスのメソッドは prototype に定義されます。
プロパティはインスタンスのプロパティになります。

class Product {
    #name: string;
    #price: number;

    constructor(name: string, price: number) {
        this.#name = name;
        this.#price = price;
    }

    getOrder(num: number): string {
        return `${this.#name} is ${this.#price * num}`
    }
}

const product1 = new Product('product1', 1000);
const product2 = new Product('product2', 2000);

console.log(typeof product1.getOrder, typeof product2.getOrder); // function, function
console.log(product1.getOrder === product2.getOrder); // true
console.log(product1.getOrder === Product.prototype.getOrder); // true
console.log(product2.getOrder === Product.prototype.getOrder); // true

// メソッドは prototype ベースなので Production.prototype.getOrder を Function.prototype.apply() で呼び出すこともできる
console.log(Product.prototype.getOrder.apply(product1, [2])); // 2000

console.log(typeof product1.getOrder); // function
// なので Function.prototype.apply() を使用できる
console.log(product1.getOrder.apply(product2, [2])); // this に production2 を指定しているので 4000 になる

TypeScript クラスを ES 5 にトランスパイル

// typescript
class Foo {
  public x: number;
  public echo: (x:number) => number;
  
  constructor(x:number) {
    this.x = x;
    // インスタンスメソッドを定義
    this.echo = x => x
  };
  // プロトタイプベースのメソッド定義
  double(): number {
      return this.x**2
  }
}

const foo1 = new Foo(3);
foo1.double();

ts.config の target を es5 にしてトランスパイルされたコード。

"use strict";
// JavaScript
var Foo = /** @class */ (function () {
    function Foo(x) {
        this.x = x;
        // インスタンスメソッドを定義
        this.echo = function (x) { return x; };
    }
    ;
    // プロトタイプベースのメソッド定義
    Foo.prototype.double = function () {
        return Math.pow(this.x, 2);
    };
    return Foo;
}());
var foo1 = new Foo(3);
foo1.double();

例外処理

例外とは、ランタイムエラーのことです。

プロを目指す人のためのTypeScript入門 p231

ランタイムエラー(例外)を発生させる

エラーを発生させたいとき、普通はまずエラーを表すオブジェクトを用意します。エラーを表すオブジェクトとは、Errorのインスタンスです。

出典:プロを目指す人のためのTypeScript入門 p231

finallyブロックの内容はエラーが発生してもしなくても実行されます。

出典:プロを目指す人のためのTypeScript入門 p238

(存在するオブジェクトの)存在しないプロパティにアクセス

  • 型に存在しない:コンパイル時の型チェック でエラーを発生させる
  • 実体(ランタイム)に存在しない: undefined を返す

※ 存在しないオブジェクトのプロパティにアクセスした場合は JavaScript はランタイムエラーを発生させます。
TypeScript は オプショナルチェイン を使ってオブジェクトが存在しない場合の処理をスキップできます。

高度な型

型推論

  1. 通常の型推論は「式からその式自体の型が推論される」という挙動を指します。

  2. 式の型が先にわかっている場合に、それをもとに式の内部に対して推論が働くことをさします。

出典:プロを目指す人のためのTypeScript入門 p157

オプショナルチェイン

JavaScriptnullundefined なオブエクトに対するプロパティアクセスはランタイムエラーになります。

例: obj?.prop // または obj?.['age']

一方で obj?.prop の場合、obj が null や undefined の場合でもランタイムエラーは発生せず、結果はundefinedとなります。 出典:プロを目指す人のためのTypeScript入門 p260

?.から続く、まとめて飛ばされるひとまとまりの部分をオプショナルチェイン(optional chain)と呼びます。 出典:プロを目指す人のためのTypeScript入門 p263

リテラル型

リテラル型はプリミティブ型をさらに細分化した型です。例えば 、 "foo" という型(文字列リテラル型ではく型です!)が存在し、これは一種のリテラル型(その中でも文字列のリテラル型)です。 出典:プロを目指す人のためのTypeScript入門 p263

// これは"foo"という文字列のみが属するリテラル型
type FooString = "foo";

// これは OK
const foo: FooString = "foo";
// あまり意味はないが以下のようにも書ける
const foo: "foo" = "foo";

// エラー: Type "bar" is not assignable to type '"foo2"'.
const bar: FooString = "bar";

リテラル型の種類

  • 文字列のリテラル型
  • 数値のリテラル型
  • 審議値のリテラル型
  • BitInt のリテラル型
const foo: "foo" = "foo";
const one: 1 = 1;
const t: true = true;
const three: 3n = 3n;

また、リテラル型は、我々が明示的に書かなくても型推論によって登場します。実は、(値としての)"foo" や 26 といったリテラルをプログラム中に書くと、これらの式の型としてリテラル型が推論されます。よって、リテラルを変数に代入することによって、その変数はリテラル型を得ることになります。

// 変数uhyoNameは"uhyo"型 const uhyoName = "uhyo"; // 半数ageは26型 const age = 26; 出典:プロを目指す人のためのTypeScript入門 p264

テンプレートリテラル型

type StringHelloLiteral = `Hello, ${string}`;
type NumberHelloLiteral = `Hello, ${number}`;

const stringValue = 'Taro';
const numberValue = 26;

const stringHello: StringHelloLiteral = `Hello, ${stringValue}`;
const numberHello: NumberHelloLiteral = `Hello, ${numberValue}`;

console.log(stringHello, numberHello);

型の絞り込み

  1. 等価演算子
  2. typeof 演算子
  3. switch 文
  4. 型述語(ユーザー定義型ガード)

型の絞り込みは、コントロールフロー解析(control flow analysis)と呼ばれることもあります。 出典:プロを目指す人のためのTypeScript入門 p271

代数的データ型を(リテラル型を使って)ユニオン型で表す

強力な型システムを持つプログラム言語はよく代数型データ型(algebraic data type ADT)の機能を持っています。 これはいくつかの種類に分類されるデータを表すための型・データ構造で、タグ付きユニオン(tagged uniton) や直和といった別名もあります。TypeScriptには代数的データ型の機能がありませんが、オブジェクト型とユニオン型を用いて擬似的に代数データ型を再現することができます。 出典:プロを目指す人のためのTypeScript入門 p274

扱うデータの形と可能性を型で正確に表現する

type Animal = {
  tag: "animal"; // 文字列 "animal" のリテラル型
  species: string;
}
type Human = {
  tag: "human";
  name: "string";
}
type User = Animal | Human
function getUserName(user: User): striing {
  switch(user.tag) {
    case "human":
      return user.name
    case
      return "名無し";
  }
}

lookup 型

type Human = {
  type: "human";
  name: string;
  age: number;
};

function setAge(human: Human, age: Human["age"]) {
  return {
    ...human,
    age
  }
}

const uhyo: Human = {
  type: "human",
  name: "uhyo",
  age: 26
};

const uhyo2 = setAge(uhyo, 27);
conosle.log(uhyo2);

keyof 型

keyof 型は、オブジェクト型からそのオブジェクトのプロパティ名の型を得る機能です。具体的には、keyof型は型Tに対してkeyof T と書きます。

プロパティが複数ある場合は、このようにそれらすべてのユニオン型となります。 出典:プロを目指す人のためのTypeScript入門 p280

重要:「プロパティが複数ある場合は、このようにそれらすべてのユニオン型となります」

keyof 型、lookup 型とジェネリクス

function get<T, K extends keyof T>(obje: T, key: K): T[K] {
  return obj[key];
}

type Human = {
  name: string;
  age: number;
}

const uhyo: Human = {
  name: "uhyo",
  age: 26
};

// uhyoName は string 型
const uhyoName = get(uhyo,, "name");
// uhyoAge は number 型
const uhyoAge = get(uhyo, "age");
  1. 第2引数 keyof T は keyof 型で具体的にはユニオン型 "name" | "age"
  2. 第2引数 K extends keyof T はユニオン型 "name" | "age" の部分型
  3. extends keyof T を省略すると戻り型 T[K] および関数内の obje[key] が Tのプロパティ( obje のプロパティ)であることが保証できない

as による型アサーション

型アサーションは式 as 型という構文で、その式の型を強制的に変えるという意味です。 .... 型アサーションでは、実際の「値」に対しては何も起こらずに、TypeScriptコンパイラが認識する「型」だけが変化します。 出典:プロを目指す人のためのTypeScript入門 p286

上記より型アサーションに誤りがあるとランタイムでエラーが発生します。
なので「型アサーションの使用はできるだけ避けるべき」(出典:プロを目指す人のためのTypeScript入門 p286 )です。

型述語(ユーザー定義型ガード)

ユーザー定義型ガードとは戻り値に型述語が書かれた関数です。

ユーザー定義型ガード(user-defined type guards)とは、型の絞り込みを自由に行うためのしくみです。 出典:プロを目指す人のためのTypeScript入門 p299

ユーザー定義型ガードは、返り値の型として型述語(type predicates)が書かれた特殊な関数です。型述語には2種類の形があります。 出典:プロを目指す人のためのTypeScript入門 p299

// 出典:プロを目指す人のためのTypeScript入門 p299
function isStringOrNumber(value: unknown): value is string | number {
    return typeof value === "string" || typeof value === "number";
}

const something: unknown = 123;

if (isStringOrNumber(something)) {
    // ここではsomethingは string | number 型
    console.log(something.toString();
}

モジュールシステム

Node.js でモジュールシステム ES Modules を使用するための設定を記載します。

  • Node.jspackage.json の type フィールドに module を指定する
    • CommonJS 形式にする場合は commonjs
  • TypeScript module コンパイラオプション[^ts-compiler-option]でモジュールシステムを指定する
    • ES Modules:es2015, es2020, es2022, esnext など
    • CommonJs: commonjs
  • TypeScriptmoduleResolution コンパイラオプションで nodenodenext を指定すると import にパス( ./relative/path.js )ではなくモジュール名(例 express, jquery )が設定されると外部モジュールとみなす

※ target コンパイルオプションはどのバージョンの JavaScript にトランスバイルするかを指定します。設定ちは es5, es6/es2015, es2024, exnext などです。exnext は TypeScript がサポートしている最新のターゲットバージョンを参照します。 ref. https://www.typescriptlang.org/tsconfig/#target

[^ts-compiler-option]: ts.config の module プロパティでも指定可能(一般的)。

Apendix

  • 「JavaScriptではオブジェクトの存在しないプロパティにアクセスした結果はundefinedとなります」(プロを目指す人のためのTypeScript入門 p91)
  • 「日次データをプログラムで扱う際は(ISO 8801 形式というフォーマットで扱うのが一般的ですが、Dateオブジェクトあこの形式にも対応しています。」(プロを目指す人のためのTypeScript入門 p123)
    • 1900-01-01T00:00:00+09:00 というフォーマット
  • プリミティブに対してプロパティアクセスを行うたびに一時的にオブジェクトが作られます(プロを目指す人のためのTypeScript入門 p129)
function foo () {
   //...
   return
       a + b
}
// return の後にセミコロンが追加さえるのでコンパイルエラーが発生します。

アンビエント宣言 (または環境宣言; ambient declaration)

ambient: 環境

JavaScriptコードを生成せず、型推論器にだけ情報を渡すのに使われるのが アンビエント宣言 (または環境宣言; ambient declaration) です。 declare のついた宣言がアンビエント宣言になります。 (declare なのに呼び名が「アンビエント」なのは不思議ですが、declareは「宣言する」という意味なので仕方なさそうです)

https://zenn.dev/qnighy/articles/9c4ce0f1b68350

Ref.