TypeScript¶
はじめに¶
本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。
01. TypeScriptとは¶
静的型付けのフロントエンド言語である。
tsconfig.jsonファイルに基づいて、TypeScriptファイルをコンパイルし、JavaScriptファイルを作成する。
拡張子として、tsとtsx (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. 型¶
プリミティブ¶
▼ 文字列 (String)¶
let str: string = "hello";
str = 0; // これはエラーになる
▼ 数値 (Number)¶
let num: number = 0;
num = "0"; // これはエラーになる
▼ 巨大な数値 (bigint)¶
let big: bigint = 10n;
big = 0; // これはエラーになる
▼ 真偽値 (Boolean)¶
let bool: boolean = true;
bool = 1; // これはエラーになる
▼ Null¶
let n: null = null;
n = undefined; // これはエラーになる
▼ Undefined¶
let u: undefined = undefined;
u = null; // これはエラーになる
▼ シンボル (Symbol)¶
let sym: symbol = Symbol();
sym = ""; // これはエラーになる
オブジェクト (Object)¶
▼ 変数¶
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
レコード (Record)¶
▼ 変数¶
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");
};
不明/任意¶
▼ unknown¶
unknownを使用した場合、それ以降の処理では型を無視して処理する。
function foo(): string | unknown {
try {
// 何らかの処理
return "success";
} catch (error: unknown) {
if (error instanceof Error) {
// unknownを使用したため、Error型として暗黙的に処理される
console.error(error.message);
} else {
// unknownを使用したため、Error型以外の型として暗黙的に処理される
console.error("Unknown error:", error);
}
return error;
}
}
▼ any¶
anyを使用した場合、それ以降の処理では型を無視して処理する。
function foo(): string | any {
try {
// 何らかの処理
return "success";
} catch (error: any) {
if (error instanceof Error) {
// anyを使用したため、Error型ではなく型無しとして暗黙的に処理される
console.error(error.message);
} else {
// anyを使用したため、型無しとして暗黙的に処理される
console.error("Unknown error:", error);
}
return error;
}
}
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!;
型ガード¶
複数の型を許容している場合に、if文を使用して型を絞り込むこと。
// string型またはnumber型を許容する
function foo(value: string | number) {
if (typeof value === "string") {
// string型として扱われる
console.log(value.toUpperCase());
} else {
// 残りのnumber型として扱われる
console.log(value.toFixed(2));
}
}
複数の型¶
▼ プロパティで複数の型を許容¶
nameプロパティはstring型とundefined型を許容している。
undefined型の方が網羅範囲が大きいため、実質undefined型として扱われる。
type User = {
id: number;
name: string | undefined;
};
const user1: User = {id: 1, name: "Alice"};
const user2: User = {id: "abc123", name: "Bob"};
▼ オブジェクト全体で複数の型を用意¶
nameプロパティがstring型のUserと、undefined型のUserWithoutNameを別々に定義している。
undefined型の方が網羅範囲が大きいため、実質undefined型として扱われる。
type User = {
id: number;
name: string;
};
type UserWithoutName = {
id: number;
name: undefined;
};
const user1: User = {id: 1, name: "Alice"};
const user2: UserWithoutName = {id: 1, name: undefined};
04. 独自の型宣言¶
比較¶
typeエイリアス宣言の方が型としての強制力が高い。
また、interface宣言はオブジェクト指向の文脈でメソッドの仕様を持たせることが多く、型の文脈では適さない(と個人的に思っている)
| 項目 | typeエイリアス宣言 | interface宣言 |
|---|---|---|
| 継承 | 基本はできない (交差型のみできる) | できる |
| 継承時の上書き | 基本はできない (交差型のみできる) | できる |
| 同名の型 | できない | できる |
| Mapped Types型 | できる | できない |
- https://typescriptbook.jp/reference/object-oriented/interface/interface-vs-type-alias#%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%81%A8%E5%9E%8B%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9%E3%81%AE%E9%81%95%E3%81%84
- https://typescriptbook.jp/reference/object-oriented/interface/interface-vs-type-alias#%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%81%A8%E5%9E%8B%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9%E3%81%AE%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91
typeエイリアス宣言¶
▼ typeエイリアス宣言とは¶
オブジェクト以外の型を宣言する場合、typeエイリアス宣言を使用する。
ただ、オブジェクトでtypeエイリアス宣言を使用してもよい。
type Foo = {
bar: number;
baz: Date;
qux: string;
};
▼ 交差型(Intersection)¶
複数の型を同時に満たす型である。
型を指定した処理では、両方の型を指定しないといけない。
type A = {foo: string};
type B = {bar: number};
// 複数の型を同時に満たす
type AB = A & B;
// 両方の型を指定する
function method(arg: AB): void {
console.log(`foo: ${arg.foo}, bar: ${arg.bar}`);
}
▼ 合併型 (Union)¶
複数の型のいずれかを満たす型である。
型を指定した処理では、いずれかの型を指定しなければならない。
type A = {foo: string};
type B = {bar: number};
// 複数の型のいずれかを満たす
type AB = A | B;
function method(arg: AB): void {
// いずれかの型を指定しないといけない
if ("foo" in arg) {
console.log(`foo: ${arg.foo}`);
return;
}
console.log(`bar: ${arg.bar}`);
return;
}
interface宣言¶
オブジェクトの型を宣言する場合、interface宣言を使用する。
ただ、オブジェクトでtypeエイリアス宣言を使用してもよい。
interface Foo {
bar: number;
baz: Date;
qux: string;
}
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";