コンテンツにスキップ

TypeScript

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. TypeScriptとは

静的型付けのフロントエンド言語である。

tsconfig.jsonファイルに基づいて、TypeScriptファイルをコンパイルし、JavaScriptファイルを作成する。

拡張子として、tstsx (TypeScript内にJSXを実装できる) を使用できる。


02 型検証の仕組み

型検証の有無

TypeScriptは静的型付け言語のため、コンパイル時に型検証 (変数、引数、返却値で指定された型に対して、渡される値の型が一致しているかを検証するプロセス) を実施する。

ほかの静的型付け言語にはない『トランスパイル』というプロセスがあるが、ここでは型検証を実施しない。

実行前のコンパイル 実行前のトランスパイル 実行時
型検証

トランスパイル

▼ コンパイル + トランスパイルの両方

function greet(name: string) {
  console.log(`Hello, ${name}!`);
}

// 引数を渡さない場合、関数にはundefinedを渡すことになる
greet();
// トランスパイルされてJavaScriptになる
// undefinedは型不一致のため、コンパイルの型検証によってエラーになる
error TS2554: Expected 1 arguments, but got 0.

▼ トランスパイルのみ

function greet(name: string) {
  console.log(`Hello, ${name}!`);
}

// 引数を渡さない場合、関数にはundefinedを渡すことになる
greet();
// トランスパイルされてJavaScriptになる
// undefinedは型不一致であるが、コンパイルの型検証がないため、エラーにならない
Hello, undefined!


02. 変数の宣言

let

記入中...


const

▼ constアサーション

constで宣言/代入された変数に関して、再代入できないようにする。

型がより複雑な配列やオブジェクトリテラルで使用すると便利である。

const obj = {
  const obj: {
    readonly name: "pikachu";
    readonly no: 25;
    readonly genre: "mouse pokémon";
    readonly height: 0.4;
    readonly weight: 6;
  }
  name: "pikachu",
  no: 25,
  genre: "mouse pokémon",
  height: 0.4,
  weight: 6.0,
} as const;


02-02. 変数の代入

プリミティブ

▼ 変数

let str: string = "hello";
str = 0;
let num: number = 0;
num = "0";
let big: bigNumber = 10n;
big = 0;
let bool: boolean = true;
bool = 1;
let n: null = null;
n = undefined;
let u: undefined = undefined;
u = null;
let sym: symbol = Symbol();
sym = "";


オブジェクト

▼ 変数

const object: {
  name: string;
  age: number;
} = {
  name: "taro",
  age: 20,
};

▼ 参照記法

const obj = {
  foo: 123,
  bar: 456,
  "baz-qux": 789,
};

const key = "bar";

// ドット記法
// キー名が静的で変わらない場合に適している
const value1 = obj.foo; // 123

// ブラケット記法
// キー名が動的な場合や記号を含む場合に適している
const value2 = obj[key]; // 456
const value3 = obj["baz-qux"]; // 789


レコード

▼ 変数

type ProfileKeys = "name" | "age";

const object: Record<ProfileKeys, string> = {
  name: "taro",
  age: "20",
};


配列

▼ 配列とは

可変的な要素数、順序は自由、型は全て同じデータ型である。

▼ 変数

const strArray: string[] = ["a", "b", "c"];

strArray.push(0);
const numArray: number[] = [1, 2, 3];

numArray.push("a");

▼ 配列の走査

const numbers = [10, 20, 30];

const total = numbers.reduce<number>(
  (result, value) => {
    // 現在の合計値(result)に、現在の要素(value)を加算する
    return result + value;
  },
  // 合計の初期値を0に設定する
  0,
);

console.log(total); // 60


タプル

▼ タプルとは

配列の一種である。

固定の要素数、順序は固定、型は自由なデータ型である。

▼ 返却値

const foo = (a: number, b: string): [number, string] => {
  return [a, b];
};

const result = foo(1, "Alice");


ジェネリクス

▼ 変数

// string[] と同じ
const str: Array<string> = ["a", "b", "c"];
// (string|number)[] と同じ
const strOrNum: Array<string | number> = ["a", "b", 1, 2];
// string{} と同じ
const str: Map<string> = {a: "a", b: "b", c: "c"};

▼ 返却値

// Promise<型>
async function asyncFn(): Promise<string> {
  // 非同期処理
  return "executed";
}

console.log(await asyncFn());

▼ 型変数(ジェネリクス)

安全なany型ともいえる。

型変数では、定義した時点で型が決まっていない。

コール時に型変数に任意の型を推論で代入し、それに合わせた引数型と返却型の関数を定義できる。

// 最初の<T>    型変数を定義
// (value: T)  引数型で型変数を使用
// : T         返却値型で型変数を使用

const foo = <T>(value: T): T {
  // ...
}
// この時点では、引数型と返却値型は決まっていない
// 変数名はなんでもよく、単語でもいい
const foo = <T>(value: T): T {
  return value;
}

// 型変数に文字を代入すると、これを推論し、string型の引数型と返却値型を定義していたことになる
foo("a");

// number型の引数型と返却値型を定義していたことになる
foo(1);
// この時点では、引数型と返却値型は決まっていない
// 変数名はなんでもよく、単語でもいい
const foo = <T>(value: T): Promise<T> => {
  return value;
};

// 型変数に文字を代入すると、これを推論し、Promise<string>型の引数型と返却値型を定義していたことになる
foo("a");

// Promise<string>型の引数型と返却値型を定義していたことになる
foo(1);
// この時点では、型変数 (T、U) の型は決まっていない
// 変数名はなんでもよく、単語でもいい
const foo = <T, U>(key1: T, key2: U): Array<T | U> => {
  return [key1, key2];
};

// 型変数に文字を代入すると、これを推論し、string型の引数型と返却値型を定義していたことになる
foo<string, string>("a", "b");

// number型の引数型と返却値型を定義していたことになる
foo<number, number>(1, 2);

// boolean型の引数型と返却値型を定義していたことになる
foo<boolean, boolean>(true, false);

// 複数の型を組み合わせることもできる
foo<string, number>("a", 1);
const measureFunctionExecutionTime = async <T>(
  fn: () => Promise<T>,
): Promise<{result: T; executionTime: number; startAtTimestamp: string}> => {
  // 計測開始
  // 計測にはperformance.now()の方が適切であるが、ISO形式に変換してタイムスタンプを取得する必要があるため、これに対応するDate.now()を使用する
  const startAt = Date.now();

  const result = await fn();

  // 計測終了
  const executionTime = Date.now() - startAt;
  const startAtTimestamp = new Date(startAt).toISOString();

  // 関数の処理結果と実行時間を返却する
  return {
    result: result,
    executionTime: executionTime,
    startAtTimestamp: startAtTimestamp,
  };
};

// この時点では、引数型と返却値型は決まっていない
// 変数名はなんでもよく、単語でもいい
const foo = <T>(value: T): Promise<T> => {
  return value;
};

// 型変数に文字を代入すると、これを推論し、fn: () => Promise<T> のTが決まる
measureFunctionExecutionTime(foo);


数値

▼ 引数

const sum = (x: number, y: number) => {
  return x + y;
};

console.log(sum(1, 2));

console.log(sum(1, "2")); // Argument of type 'string' is not assignable to parameter of type 'number'.

console.log(sum(1)); // Expected 2 arguments, but got 1.

▼ 返却値

const sum = (x: number, y: number): number => {
  return x + y;
};


返却値なし

// 返却値がないという型
const logger = (): void => {
  console.log("log");
};


03. 型推論

暗黙的

let name = "John"; // 変数nameは文字列として推論されます
let age = 30; // 変数ageは数値として推論されます
let isProgrammer = true; // 変数isProgrammerはブール値として推論されます


明示的

let name: string = "John";
let age: number = 30;
let isProgrammer: boolean = true;


型アサーション

▼ 型アサーション

型を上書きする。

キャストではないらしい。

▼ as構文

const value: string | number = "this is a string";
const strLength: number = (value as string).length;

▼ アングルブラケット構文

const value: string | number = "this is a string";
const strLength: number = (<string>value).length;

▼ 非nullアサーション

変数の値がundefinedだった場合に、例外をスローする。

const foo: string = process.env.FOO!;


04. 独自の型宣言

typeエイリアス宣言

オブジェクト以外の型を宣言する場合、typeエイリアス宣言を使用する。

ただ、オブジェクトでtypeエイリアス宣言を使用してもよい。

type Foo = {
  bar: number;
  baz: Date;
  qux: string;
};


interface宣言

オブジェクトの型を宣言する場合、interface宣言を使用する。

ただ、オブジェクトでtypeエイリアス宣言を使用してもよい。


05. 環境変数の定義

出力

▼ 言語の実行環境

  • exportコマンドで出力する
  • コンテナの環境変数として出力する

▼ dotenvパッケージ

dotenvパッケージ

なお、依存パッケージが増えてしまうため、代替の方法があるならそちらの方が良い。

import dotenv from "dotenv";

// .envファイルを読み込む
dotenv.config();

// なんらかの実装


型の定義

interface Env {
  DATABASE_NAME: string;
  DATABASE_PORT?: number;
}

const myEnv: Env = {
  DATABASE_NAME: process.env.DATABASE_NAME || "",
  DATABASE_PORT: process.env.DATABASE_PORT
    ? parseInt(process.env.DATABASE_PORT)
    : undefined,
};


06. パッケージ

import

▼ importとは

import {logger} from "./logger";


export

▼ exportとは

export {logger} from "./logger";

▼ index.ts

各ディレクトリのエントリポイントとして使える。

index.tsファイルでexportしておくと、コールする側がディレクトリ単位でインポートできるようになる。

// utils/index.ts
export {logger} from "./logger";
export {logger} from "./errorHandler";

アスタリスクで一括でエクスポートしてもよい。

// utils/index.ts
export * from "./logger";
export * from "./errorHandler";
// ファイルを個別に指定する必要がなくなる
import {fooLogger, fooErrorHandler} from "~/utils";