■BASE64エンコード (JavaScript)
ECMAScript 2024(ES2024)
// 文字列をバイナリ(Uint8Array)に変換
const bytes = new TextEncoder().encode("こんにちは");
// URLセーフなBase64(パディング = なし)への変換
const urlSafeBase64 = bytes.toBase64({ alphabet: "base64url", omitPadding: true });
■typeとinterfaceの使い分けと使い方
どちらもほぼ同じように使えるが
interfaceはオブジェクトやクラスの形状を定義し
type(型のエイリアス)はinterfaceでは表現できない複雑な「型」を扱うのに使用する。
1.typeが扱える複雑な型
1.1.プリミティブ型(Primitive Type)
1.2.ユニオン型(Union Type)
type Status = "idle" | "loading" | "success" | "error";
1.3.タプル型(Tuple Type)
実際はだたの配列(Array)
オブジェクト表記でデータを持つより軽い。
可読性が下がらない場所か最適化が必要な場所で使う。
push等による型崩壊を防ぐため、原則 `readonly` を付与する。
type Point = readonly [x: number, y: number];
// 使用例
const path: Point[] = [
[0, 0],
[10, 20],
[35, 40],
];
for (let i = 0; i < path.length; i++) {
const [x, y] = path[i];
console.log(`X: ${x}, Y: ${y}`);
}
// 使用例2
function getCenter(width: number, height: number): Point {
return [width / 2, height / 2];
}
const [centerX, centerY] = getCenter(1920, 1080);
1.4.マップドタイプ(Mapped Types)などの高度な型操作
type ReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
1.5.マップドタイプの応用
型引数(ジェネリクス)を用いた条件付き型による再帰的な定義
- infer(型の自動推論・抽出)
条件付き型(extends ? :)の右側で使い
型の一部分を「変数」として型抜きする機能
type DeepReadonly<T> = T extends Function
? T
: T extends Map<infer K, infer V>
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
: T extends Set<infer U>
? ReadonlySet<DeepReadonly<U>>
: T extends Array<infer E>
? ReadonlyArray<DeepReadonly<E>>
: T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> }
: T;
※上記は標準では用意されていない自前の深層完全不変の定義。
内部全てが不変(readonly)になる。はず。
1.6.判別共用体型(Discriminated Union Type)
共通するリテラル型のプロパティが「判別子」として扱われる。
判別子は複数可能。
interface NetworkSuccess {
status: "success"; // 判別子(Discriminant )
data: string[];
}
interface NetworkError {
status: "error"; // 判別子(Discriminant )
error: Error;
}
// 判別共用体型を type で合成
type NetworkResponse = NetworkSuccess | NetworkError;
or
type NetworkResponse =
| { status: "success"; data: string[] }
| { status: "error"; error: Error };
// 使用例
function handleResponse(res: NetworkResponse) {
// res.status の値によって、コンパイラが型を確定させる
if (res.status === "success") {
console.log(res.data); // ここで data にアクセス可能
}
else {
console.error(res.error.message); // ここで error にアクセス可能
}
}
2.継承・合成のやり方
2.1.interface同士(extends)
interface Animal {
name: string;
}
interface Dog extends Animal {
bark(): void;
}
2.2.type同士(& インターセクション)
type Flyable = {
fly(): void;
};
type Swimmable = {
swim(): void;
};
type Duck = Flyable & Swimmable;
type Duck2 = Duck & {
sleep(): void;
};
2.3.interfaceとtypeの相互合成
// 1. interface が type を extends する
type Identifiable = {
readonly id: string;
};
interface Product extends Identifiable {
price: number;
}
// 2. type が interface を & で合成する
interface Serializable {
serialize(): string;
}
type Config = Serializable & {
version: string;
};
※同名プロパティが存在する場合はエラーになるがそれぞれタイミングが異なる。
3.関数の定義
3.1.type での定義:関数型リテラル (Function Type Literal)
通常の「純粋な関数」はこれ一択。
type LogFn = (message: string) => void;
const logger1: LogFn = (msg) => console.log(msg);
3.2.interface での定義:コールシグネチャ (Call Signature)
関数であり、かつプロパティも持つハイブリッド型を作る時に使う。
interface LogInterface {
(message: string): void; // コールシグネチャ
level: number; // プロパティ
}
const logger2: LogInterface = Object.assign(
(msg: string) => {
console.log(`[Level ${logger2.level}] ${msg}`);
},
{ level: 3 }
);
結論:定義の仕方は違っても呼び出し側のコードは `logger("msg")` で完全に同じ。
違いは「型を定義する側」が、関数にプロパティを持たせたいかどうかだけ。
3.3.オブジェクトのプロパティとしての定義
メソッドシグネチャ (Method Signature)
関数単体ではなく、「オブジェクトが持つ関数(メソッド)」の形状を定義する構文
interface Drawer {
draw(): void; // 「メソッドシグネチャ」
}
■for文について (JavaScript)
const items = ["a", "b", "c"];
const itemObj = { a: 1, b:2, c:3 };
文法
[1] for (let i = 0; i < items.length; i++) { xxxx }
[2] for (const item of items) { xxxx }
[3] for (const [i, item] of items.entries()) { xxxx }
[4] items.forEach((item, i) => { xxxx });
[5] for (const [key, value] of Object.entries(itemObj)) { xxxx }
解説
[1]: 最速・最軽量
[2]: 標準
[3 - 4]: 他言語とは違いCPUとメモリとGCに負荷をかけるので高負荷領域では使用NG。
[5]: 外界との境界(バリデーション、シリアライズ)でしか使用してはならない
■軽量Enum
1. 名称と内部値を列挙したオブジェクトを作る(末尾の as const が必須)
2. 同名で内部値のユニオン型を作る(0 | 1 | 2 | 3)
3. 【任意】名称のユニオン型を作る("SUCCESS" | "WARNING" ...)
- マップ型のキー定義として使える。
const RequestStatus = {
SUCCESS: 0,
WARNING: 1,
ERROR: 2,
FATAL: 3,
} as const;
type RequestStatus = typeof RequestStatus[keyof typeof RequestStatus];
type RequestStatusKey = keyof typeof RequestStatus;
// 使用例
function handleStatus(status: RequestStatus): void {
if (status === RequestStatus.FATAL) {
console.log("FATAL");
}
}
■マップ定数の作り方
1.ROLES: 原本リスト。
2.ROLE: ROLESから抽出したユニオン型。
3.ROLE_PERMISSIONS: ROLE型をキーとしたマッピング定数。
4.isRole: 型ガード関数(型述語タイプ)
実行時の型ガードはTypeScript世界のROLEでは行えない
const ROLES = ["admin", "manager", "user"] as const;
type ROLE = typeof ROLES[number]; // --> "admin" | "manager" | "user"
// const ROLE_PERMISSIONS: Readonly<Record<ROLE, readonly string[]>> = {
// or
const ROLE_PERMISSIONS: Readonly<{ [K in ROLE]: readonly string[] }> = {
"admin": ["Read", "Write", "Delete", "Execute"],
"manager": ["Read", "Write", "Delete"],
"user": ["Read"],
};
function isRole(value: string): value is ROLE {
return (ROLES as readonly string[]).includes(value);
}
// 使用例
let inputData = "abc"; // inputDataはstring型
console.log(`'${inputData}' isRole: ${isRole(inputData)}`);
inputData = "manager"; // inputDataはstring型
if( isRole(inputData) ) {
// 型ガードを通過したのでinputDataはROLE型として扱われる
const permisson = ROLE_PERMISSIONS[inputData];
console.log(`${inputData}: ${permisson}`);
}
// 使用例
console.log("-- loop");
// 型定義的にROLESはROLE_PERMISSIONSのキーリストでもある
for (const role of ROLES) {
const permisson = ROLE_PERMISSIONS[role];
console.log(`${role}: ${permisson}`);
}
■関数の引数と戻り値の不変性(イミュータブル)表現方法
type DeepReadonly<T> = T extends Function
? T
: T extends Map<infer K, infer V>
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
: T extends Set<infer U>
? ReadonlySet<DeepReadonly<U>>
: T extends Array<infer E>
? ReadonlyArray<DeepReadonly<E>>
: T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> }
: T;
function processData(
a: User[], // [1] 可変: 配列の変更、Userの変更OK
b: Readonly<User>[], // [2] 要素不変: 配列の変更OK、Userの変更NG
c: readonly User[], // [3] 配列不変: 配列の変更NG、Userの変更OK
d: readonly Readonly<User>[], // [4] 浅層完全不変: 配列の変更、User変更NG
e: readonly DeepReadonly<User>[], // [5] 深層完全不変: [4] + User内部の全階層でNG
): User[] // [1]と同じ
): Readonly<User>[] // [2]と同じ
): readonly User[] // [3]と同じ
): readonly Readonly<User>[] // [4]と同じ
): readonly DeepReadonly<User>[] // [5]と同じ
readonly - TypeScript 2.0 2016/09, 3.4 2019/03
Readonly - TypeScript 2.1 2016/12
Readonlyは直下のプロパティのみreadonlyとなる。
ネストしてる2層目以降は対象外。
■関数で名前付き引数風を扱う
function splitTokens(
value: string,
{ // デフォルト値
separator = ":",
prefix = "",
suffix = "",
}: { // 型定義
separator?: string;
prefix?: string;
suffix?: string;
} = {}, // 名前付き引数風を丸ごと省略可能にする
): string[] {
// 使用例
splitTokens("txt:doc:pdf");
splitTokens("txt:doc:pdf", { prefix: "." });