2020-12-06 :

nuxt-ts day7:TypeScript Deep Dive を読む 3


座学3日目。ここの基礎部分をしっかりやっておかねばなので、時間をかけてやっていこう。

https://typescript-jp.gitbook.io

知らなかったことをメモしていく。

ファイルモジュール

JavaScriptではデフォルトでグローバルの名前空間に追加される。なので、名前が競合することを避けるためにファイルモジュールを使うことが推奨される。

TypeScriptファイルにimportまたはexportが存在する場合、そのファイル内に閉じたローカルスコープが作成される。

ESモジュールの構文

名前を変更してエクスポートすることもできる。インポート時も同様。

// `foo.ts`ファイル
let someVar = 123;
export { someVar as aDifferentName };

import * asを使って1つの名前にモジュールすべてをインポートする

// `bar.ts`ファイル
import * as foo from './foo';
// `foo.someVar`や`foo.SomeType`など`foo.ts`がエクスポートしているものを何でも利用できます

別のモジュールから一部のものを再エクスポートする

export { someVar } from './foo';

デフォルトエクスポート/インポート

// 何らかの変数
export default someVar = 123;
// または関数
export default function someFunction() { }
// またはクラス
export default class SomeClass { }
import someLocalNameForThisFile from "../foo";

これはチーム開発だとあまり推奨されていないみたい。これは以下の記事が分かりやすかった。

なぜ default export を使うべきではないのか?

exportは必ず名前付きでするようにして、import時もその名前で読み込む。確かに理にかなっているように思う。書きやすさよりも読みやすさを優先するという話、なのかな。

モジュールのパス解決

2つの異なる種類のモジュールがあり、それらはimport文のパス部分によって区別される。主な違いは、モジュールがファイルシステム上でどのように解決されるかにある。

  • 相対パスのモジュール (.で始まるパス:./someFile、../../someFolder/someFileなど)
  • 動的に参照されるモジュール ('core-js'、'typestyle'、'react'、'react / core'など)

相対パスについては基本の相対パスに従うのみであるが、動的に参照される場合、以下のように検索される。

  • import * as foo from 'foo'と書いた場合、以下の場所が順番にチェックされます
    • ./node_modules/foo
    • ../node_modules/foo
    • ../../node_modules/foo
    • ファイルシステムのルートに到達するまで続く

ジェネリック型

型が何であれ、プッシュされているものの型とポップされたものの型は同じでなければならないときを考える。これはジェネリクスで解決ができる。共通化できるので便利というやつですね。

/** A class definition with a generic parameter */
class Queue<T> {
  private data = [];
  push(item: T) { this.data.push(item); }
  pop(): T | undefined { return this.data.shift(); }
}

/** Again sample usage */
const queue = new Queue<number>();
queue.push(0);
queue.push("1"); // ERROR : cannot push a string. Only numbers allowed

単純なジェネリックを使うときは T、U、Vを使うのが慣習らしい。

API呼び出しのときも、こんな感じに使うことができる。

const getJSON = <T>(config: {
    url: string,
    headers?: { [key: string]: string },
  }): Promise<T> => {
    const fetchConfig = ({
      method: 'GET',
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      ...(config.headers || {})
    });
    return fetch(config.url, fetchConfig)
      .then<T>(response => response.json());
  }
type LoadUsersResponse = {
  users: {
    name: string;
    email: string;
  }[];  // array of user objects
}
function loadUsers() {
  return getJSON<LoadUsersResponse>({ url: 'https://example.com/users' });
}

依然としてアノテーションは明示しなければならないが、戻り値としてのPromise<T>は、Promise<any>よりも断然優れている。積極的に使っていきたい。

Union型

literal型のメンバを持つクラスがある場合、そのプロパティを使用して、Union型のメンバを判別することができる。

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
type Shape = Square | Rectangle;
function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
        default: const _exhaustiveCheck: never = s;
    }
}

具体的にどこに使えるかは思いつかないけど、知っといて損は無さそうだ。

今日はここまで。明日には座学終わりそう。