TypeScript 5.x新機能完全ガイド2026 - Decorators、const型パラメータ、satisfies、using宣言、推論改善徹底解説
TypeScript 5.x新機能完全ガイド2026
TypeScript 5.xシリーズは、多くの革新的機能を導入しました。本記事では、5.0から5.6までの主要機能を実践的なコード例とともに徹底解説します。
目次
- Decorators(5.0)
- const型パラメータ(5.0)
- satisfies演算子の進化(5.0+)
- using宣言とDisposable(5.2)
- 推論改善(5.0〜5.6)
- Import Attributes(5.3)
- 型述語の改善(5.5)
- パフォーマンス改善
- 実践的なパターン
Decorators(5.0)
Stage 3 Decoratorsのサポート
TypeScript 5.0でECMAScript Stage 3のデコレータ仕様に対応しました。
// クラスデコレータ
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class BugReport {
type = "report";
title: string;
constructor(title: string) {
this.title = title;
}
}
// メソッドデコレータ
function log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`Result:`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// ログ出力:
// Calling add with args: [2, 3]
// Result: 5
高度なデコレータパターン
// パラメータを受け取るデコレータ
function throttle(milliseconds: number) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
let lastRun = 0;
let timeout: NodeJS.Timeout | null = null;
descriptor.value = function (...args: any[]) {
const now = Date.now();
if (timeout) {
clearTimeout(timeout);
}
if (now - lastRun >= milliseconds) {
lastRun = now;
return originalMethod.apply(this, args);
} else {
timeout = setTimeout(() => {
lastRun = Date.now();
originalMethod.apply(this, args);
}, milliseconds - (now - lastRun));
}
};
return descriptor;
};
}
class SearchComponent {
@throttle(300)
handleSearch(query: string) {
console.log("Searching for:", query);
// API呼び出しなど
}
}
プロパティデコレータ
// バリデーションデコレータ
function MinLength(min: number) {
return function (target: any, propertyKey: string) {
let value: string;
const getter = function () {
return value;
};
const setter = function (newValue: string) {
if (newValue.length < min) {
throw new Error(
`${propertyKey} must be at least ${min} characters long`
);
}
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
};
}
function Email() {
return function (target: any, propertyKey: string) {
let value: string;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const getter = function () {
return value;
};
const setter = function (newValue: string) {
if (!emailRegex.test(newValue)) {
throw new Error(`${propertyKey} must be a valid email`);
}
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
};
}
class User {
@MinLength(3)
username!: string;
@Email()
email!: string;
}
const user = new User();
user.username = "ab"; // エラー: username must be at least 3 characters long
user.email = "invalid"; // エラー: email must be a valid email
デコレータコンポジション
// 複数のデコレータを組み合わせる
function Autobind(
_target: any,
_propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const adjustedDescriptor: PropertyDescriptor = {
configurable: true,
enumerable: false,
get() {
return originalMethod.bind(this);
},
};
return adjustedDescriptor;
}
function Memoize(
_target: any,
_propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const cache = new Map();
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("Returning cached result");
return cache.get(key);
}
const result = originalMethod.apply(this, args);
cache.set(key, result);
return result;
};
return descriptor;
}
class DataService {
@Autobind
@Memoize
expensiveCalculation(n: number): number {
console.log("Computing...");
let result = 0;
for (let i = 0; i < n; i++) {
result += i;
}
return result;
}
}
const service = new DataService();
const boundMethod = service.expensiveCalculation;
boundMethod(1000); // Computing... と表示
boundMethod(1000); // Returning cached result と表示
const型パラメータ(5.0)
基本的な使い方
// const型パラメータで literal型を保持
function createConfig<const T>(config: T) {
return config;
}
// 従来の方法
const config1 = createConfig({ mode: "development", port: 3000 });
// 型: { mode: string; port: number; }
// const型パラメータを使用
const config2 = createConfig({ mode: "development", port: 3000 } as const);
// 型: { readonly mode: "development"; readonly port: 3000; }
// TypeScript 5.0以降は自動的にliteral型を推論
function createConfigAuto<const T>(config: T) {
return config;
}
const config3 = createConfigAuto({ mode: "development", port: 3000 });
// 型: { mode: "development"; port: 3000; }(自動的にliteral型)
実践的な例
// ルート定義
function defineRoutes<const T extends Record<string, string>>(routes: T) {
return routes;
}
const routes = defineRoutes({
home: "/",
about: "/about",
users: "/users/:id",
} as const);
// 型: {
// readonly home: "/";
// readonly about: "/about";
// readonly users: "/users/:id";
// }
// 型安全なルーティング
type Route = (typeof routes)[keyof typeof routes];
// 型: "/" | "/about" | "/users/:id"
function navigate(route: Route) {
console.log(`Navigating to ${route}`);
}
navigate(routes.home); // OK
navigate("/contact"); // エラー: "/contact" は Route 型に割り当てできません
配列での使用
function tuple<const T extends readonly unknown[]>(...args: T) {
return args;
}
// 従来
const arr1 = tuple("hello", 42, true);
// 型: (string | number | boolean)[]
// const型パラメータ
const arr2 = tuple("hello", 42, true);
// 型: readonly ["hello", 42, true]
// より厳密な型チェックが可能
function processUser<const T extends readonly [string, number, boolean]>(
data: T
) {
const [name, age, active] = data;
// name: string, age: number, active: boolean
}
processUser(["Alice", 30, true]); // OK
processUser(["Bob", 25]); // エラー: 引数が足りない
satisfies演算子の進化
基本的な使い方
// satisfies演算子で型チェックしつつ literal型を保持
type Color = "red" | "green" | "blue";
type ColorConfig = {
primary: Color;
secondary: Color;
accent?: Color;
};
// 従来の方法(型アノテーション)
const config1: ColorConfig = {
primary: "red",
secondary: "green",
accent: "blue",
};
// config1.primary の型は Color("red" | "green" | "blue")
// satisfies演算子を使用
const config2 = {
primary: "red",
secondary: "green",
accent: "blue",
} satisfies ColorConfig;
// config2.primary の型は "red"(literal型を保持)
// 型エラーを検出しつつ literal型を維持
const config3 = {
primary: "red",
secondary: "yellow", // エラー: "yellow" は Color に割り当てできません
} satisfies ColorConfig;
オブジェクトキーの型安全性
type AllowedKeys = "name" | "age" | "email";
const user = {
name: "Alice",
age: 30,
email: "alice@example.com",
} satisfies Record<AllowedKeys, unknown>;
// user.name の型は string(literal型ではない)
// しかし、不正なキーはコンパイルエラー
const invalidUser = {
name: "Bob",
age: 25,
phone: "123-456", // エラー: phone は AllowedKeys にない
} satisfies Record<AllowedKeys, unknown>;
関数の戻り値での使用
type ApiResponse<T> = {
data: T;
status: number;
message: string;
};
function fetchUser(id: string) {
return {
data: { id, name: "Alice", age: 30 },
status: 200,
message: "Success",
} satisfies ApiResponse<{ id: string; name: string; age: number }>;
}
const response = fetchUser("123");
// response.data.name の型は string
// response.status の型は 200(literal型)
配列要素の型チェック
type Task = {
id: number;
title: string;
completed: boolean;
};
const tasks = [
{ id: 1, title: "Buy groceries", completed: false },
{ id: 2, title: "Write code", completed: true },
{ id: 3, title: "Exercise", completed: false },
] satisfies Task[];
// 各要素の literal型を保持
tasks[0].completed; // 型: false
tasks[1].completed; // 型: true
using宣言とDisposable(5.2)
基本的な使い方
// Disposableインターフェースを実装
class FileHandle implements Disposable {
constructor(private filename: string) {
console.log(`Opening file: ${filename}`);
}
write(data: string) {
console.log(`Writing to ${this.filename}: ${data}`);
}
[Symbol.dispose]() {
console.log(`Closing file: ${this.filename}`);
}
}
function processFile() {
using file = new FileHandle("data.txt");
file.write("Hello, World!");
// スコープを抜ける時に自動的に [Symbol.dispose] が呼ばれる
}
processFile();
// 出力:
// Opening file: data.txt
// Writing to data.txt: Hello, World!
// Closing file: data.txt
データベース接続の管理
class DatabaseConnection implements AsyncDisposable {
private connection: any;
constructor(private connectionString: string) {}
async connect() {
console.log(`Connecting to ${this.connectionString}`);
// 実際の接続処理
this.connection = {}; // 仮の接続オブジェクト
}
async query(sql: string) {
console.log(`Executing query: ${sql}`);
// クエリ実行
return [];
}
async [Symbol.asyncDispose]() {
console.log("Closing database connection");
// 接続を閉じる
this.connection = null;
}
}
async function fetchUsers() {
await using db = new DatabaseConnection("postgresql://localhost/mydb");
await db.connect();
const users = await db.query("SELECT * FROM users");
return users;
// 関数を抜ける時に自動的に接続を閉じる
}
リソース管理のベストプラクティス
// ロック機構の実装
class Lock implements Disposable {
private locked = false;
acquire() {
if (this.locked) {
throw new Error("Lock is already acquired");
}
this.locked = true;
console.log("Lock acquired");
}
release() {
this.locked = false;
console.log("Lock released");
}
[Symbol.dispose]() {
if (this.locked) {
this.release();
}
}
}
class ResourceManager {
private lock = new Lock();
criticalSection() {
using _ = this.lock;
this.lock.acquire();
// クリティカルセクション
console.log("Performing critical operation");
// 例外が発生してもロックは自動的に解放される
if (Math.random() > 0.5) {
throw new Error("Something went wrong");
}
}
}
const manager = new ResourceManager();
try {
manager.criticalSection();
} catch (error) {
console.error(error);
}
// Lock は必ず解放される
複数リソースの管理
class Transaction implements AsyncDisposable {
constructor(private id: string) {
console.log(`Transaction ${id} started`);
}
async commit() {
console.log(`Transaction ${this.id} committed`);
}
async rollback() {
console.log(`Transaction ${this.id} rolled back`);
}
async [Symbol.asyncDispose]() {
console.log(`Transaction ${this.id} disposed`);
}
}
async function performDatabaseOperation() {
await using tx1 = new Transaction("tx1");
await using tx2 = new Transaction("tx2");
try {
// 操作を実行
await tx1.commit();
await tx2.commit();
} catch (error) {
// エラー時は両方ロールバック
await tx1.rollback();
await tx2.rollback();
throw error;
}
// スコープを抜ける時、tx2、tx1の順(逆順)で dispose される
}
推論改善(5.0〜5.6)
テンプレートリテラル型の推論改善
// TypeScript 5.0+
type Route = `/users/${string}` | `/posts/${string}`;
function navigate(route: Route) {
console.log(`Navigating to ${route}`);
}
navigate("/users/123"); // OK
navigate("/posts/456"); // OK
navigate("/products/789"); // エラー
// より高度なテンプレートリテラル型
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/api/${string}`;
type ApiRoute = `${HTTPMethod} ${Endpoint}`;
const routes: ApiRoute[] = [
"GET /api/users",
"POST /api/users",
"PUT /api/users/123",
"DELETE /api/users/123",
];
条件型の推論改善
// 再帰的な条件型
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type Test1 = Flatten<number[]>; // number
type Test2 = Flatten<number[][]>; // number
type Test3 = Flatten<number[][][]>; // number
// より複雑な推論
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
type User = {
name: string;
address: {
street: string;
city: string;
};
};
type ReadonlyUser = DeepReadonly<User>;
// {
// readonly name: string;
// readonly address: {
// readonly street: string;
// readonly city: string;
// };
// }
タプル型の推論改善
// TypeScript 5.2+
function concat<T extends readonly unknown[], U extends readonly unknown[]>(
arr1: T,
arr2: U
): [...T, ...U] {
return [...arr1, ...arr2];
}
const result = concat([1, 2], ["a", "b"]);
// 型: [number, number, string, string]
// 可変長引数の推論
function curry<T extends unknown[], U>(
fn: (...args: T) => U
): (...args: T) => U {
return fn;
}
const add = curry((a: number, b: number, c: number) => a + b + c);
// add の型: (a: number, b: number, c: number) => number
型ガードの推論改善
// TypeScript 5.5+
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
function process(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase()); // value は string
} else if (isNumber(value)) {
console.log(value.toFixed(2)); // value は number
}
}
// より高度な型ガード
type Success<T> = { success: true; data: T };
type Failure = { success: false; error: string };
type Result<T> = Success<T> | Failure;
function isSuccess<T>(result: Result<T>): result is Success<T> {
return result.success;
}
function handleResult<T>(result: Result<T>) {
if (isSuccess(result)) {
console.log(result.data); // result は Success<T>
} else {
console.log(result.error); // result は Failure
}
}
Import Attributes(5.3)
JSON インポート
// JSON ファイルをインポート
import data from "./config.json" with { type: "json" };
// 型安全なJSONインポート
interface Config {
apiUrl: string;
timeout: number;
}
import config from "./config.json" with { type: "json" } as Config;
console.log(config.apiUrl);
console.log(config.timeout);
CSS Modules
// CSS Modulesのインポート
import styles from "./Button.module.css" with { type: "css" };
function Button() {
return <button className={styles.primary}>Click me</button>;
}
カスタムインポート属性
// カスタムローダーでの使用
import wasm from "./module.wasm" with { type: "webassembly" };
async function initWasm() {
const instance = await WebAssembly.instantiate(wasm);
return instance.exports;
}
型述語の改善(5.5)
複雑な型ガードの推論
// TypeScript 5.5+
interface Dog {
type: "dog";
bark(): void;
}
interface Cat {
type: "cat";
meow(): void;
}
type Animal = Dog | Cat;
function isDog(animal: Animal): animal is Dog {
return animal.type === "dog";
}
function processAnimal(animal: Animal) {
if (isDog(animal)) {
animal.bark(); // animal は Dog
} else {
animal.meow(); // animal は Cat(自動的に推論)
}
}
// 配列の型ガード
function isStringArray(arr: unknown[]): arr is string[] {
return arr.every((item) => typeof item === "string");
}
function processArray(arr: unknown[]) {
if (isStringArray(arr)) {
arr.forEach((str) => console.log(str.toUpperCase()));
}
}
パフォーマンス改善
ビルド速度の向上
// tsconfig.json
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo",
// TypeScript 5.0+ の最適化
"moduleDetection": "force",
"isolatedModules": true,
// より高速な型チェック
"skipLibCheck": true,
"skipDefaultLibCheck": true
}
}
プロジェクトリファレンスの活用
// tsconfig.base.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true
}
}
// packages/core/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"]
}
// packages/app/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"references": [
{ "path": "../core" }
],
"compilerOptions": {
"outDir": "dist"
}
}
実践的なパターン
型安全なイベントエミッター
type EventMap = {
userLoggedIn: { userId: string; timestamp: Date };
userLoggedOut: { userId: string };
dataUpdated: { id: string; data: unknown };
};
class TypedEventEmitter<T extends Record<string, any>> {
private listeners = new Map<keyof T, Set<Function>>();
on<K extends keyof T>(event: K, listener: (data: T[K]) => void) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(listener);
}
emit<K extends keyof T>(event: K, data: T[K]) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.forEach((listener) => listener(data));
}
}
off<K extends keyof T>(event: K, listener: (data: T[K]) => void) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.delete(listener);
}
}
}
const emitter = new TypedEventEmitter<EventMap>();
emitter.on("userLoggedIn", (data) => {
console.log(`User ${data.userId} logged in at ${data.timestamp}`);
});
emitter.emit("userLoggedIn", {
userId: "123",
timestamp: new Date(),
});
// 型エラー
emitter.emit("userLoggedIn", {
userId: 123, // エラー: number は string に割り当てできません
timestamp: new Date(),
});
ビルダーパターン
class QueryBuilder<T> {
private whereClause: string[] = [];
private orderByClause: string | null = null;
private limitValue: number | null = null;
where(condition: string): this {
this.whereClause.push(condition);
return this;
}
orderBy(field: keyof T, direction: "ASC" | "DESC" = "ASC"): this {
this.orderByClause = `${String(field)} ${direction}`;
return this;
}
limit(value: number): this {
this.limitValue = value;
return this;
}
build(): string {
let query = "SELECT * FROM table";
if (this.whereClause.length > 0) {
query += ` WHERE ${this.whereClause.join(" AND ")}`;
}
if (this.orderByClause) {
query += ` ORDER BY ${this.orderByClause}`;
}
if (this.limitValue !== null) {
query += ` LIMIT ${this.limitValue}`;
}
return query;
}
}
type User = {
id: number;
name: string;
email: string;
createdAt: Date;
};
const query = new QueryBuilder<User>()
.where("age > 18")
.where("active = true")
.orderBy("createdAt", "DESC")
.limit(10)
.build();
console.log(query);
// SELECT * FROM table WHERE age > 18 AND active = true ORDER BY createdAt DESC LIMIT 10
型安全なAPI クライアント
type ApiEndpoints = {
"/users": {
GET: { response: User[] };
POST: { body: { name: string; email: string }; response: User };
};
"/users/:id": {
GET: { params: { id: string }; response: User };
PUT: {
params: { id: string };
body: Partial<User>;
response: User;
};
DELETE: { params: { id: string }; response: void };
};
};
class ApiClient {
async request<
Path extends keyof ApiEndpoints,
Method extends keyof ApiEndpoints[Path]
>(
method: Method,
path: Path,
options?: ApiEndpoints[Path][Method] extends { params: infer P }
? { params: P }
: ApiEndpoints[Path][Method] extends { body: infer B }
? { body: B }
: {}
): Promise<
ApiEndpoints[Path][Method] extends { response: infer R } ? R : never
> {
// 実際のHTTPリクエスト処理
const url = this.buildUrl(path, options?.params);
const response = await fetch(url, {
method: method as string,
body: options?.body ? JSON.stringify(options.body) : undefined,
});
return response.json();
}
private buildUrl(path: string, params?: Record<string, string>): string {
let url = `https://api.example.com${path}`;
if (params) {
Object.entries(params).forEach(([key, value]) => {
url = url.replace(`:${key}`, value);
});
}
return url;
}
}
const api = new ApiClient();
// 型安全なAPIコール
const users = await api.request("GET", "/users");
// users の型は User[]
const user = await api.request("GET", "/users/:id", {
params: { id: "123" },
});
// user の型は User
await api.request("POST", "/users", {
body: { name: "Alice", email: "alice@example.com" },
});
// 型エラー
await api.request("POST", "/users", {
body: { name: "Bob" }, // エラー: email が必要
});
まとめ
TypeScript 5.xシリーズは、型システムの表現力とパフォーマンスを大幅に向上させました。
主要な新機能:
- Decorators: メタプログラミングの標準化
- const型パラメータ: literal型の自動推論
- satisfies: 型チェックとliteral型の両立
- using宣言: 自動リソース管理
- 推論改善: より正確な型推論
2026年のベストプラクティス:
- Decoratorsで横断的関心事を分離
- const型パラメータで型安全性を向上
- using宣言でリソースリークを防止
- 型述語で正確な型ガード
- Import Attributesで静的アセット管理
これらの機能を活用して、より型安全で保守性の高いTypeScriptコードを書きましょう。