TypeScript 5.8+ Stage 3 Decorators: 本番環境でのメタプログラミング、DI、およびruntimeバリデーション

目次(8 項目)
TypeScript 5.8以降では、ECMAScript Stage 3 Decoratorsがネイティブにサポートされ、レガシーなexperimentalDecoratorsフラグからの重要な転換点となります。このネイティブ実装は、メタプログラミングのための堅牢で標準化されたメカニズムを提供し、非標準機能やポリフィルに頼ることなく、依存性注入(DI)やランタイム検証のような強力なパターンを可能にします。このガイドでは、これらのデコレーターの実用的なアプリケーションに焦点を当て、そのアーキテクチャ、実装、および本番環境での有用性について詳しく説明します。
Stage 3 Decoratorsを理解する
Stage 3 Decoratorsは、クラス要素がインスタンス化される時ではなく、定義される時に動作します。これらは特定のコンテキストを受け取り、装飾された要素の変更または置換を可能にする関数です。experimentalDecoratorsとは異なり、新しい仕様は、プライベートフィールドへのアクセスや初期化子の追加機能など、より予測可能で強力なAPIを提供します。
デコレーターの型とシグネチャ
デコレーター関数のシグネチャは、ターゲットによって異なります。
-
クラスデコレーター: クラス宣言に適用されます。
typescripttype ClassDecorator = <T extends Function>( value: T, context: ClassDecoratorContext ) => T | void;contextは、kind: 'class'、name、およびaddInitializerを提供します。 -
メソッドデコレーター: クラス内のメソッドに適用されます。
typescripttype MethodDecorator = <T extends Function>( value: T, context: ClassMethodDecoratorContext ) => T | void;contextは、kind: 'method'、name、static、private、access、およびaddInitializerを提供します。 -
ゲッター/セッターデコレーター: アクセサーに適用されます。
typescripttype AccessorDecorator = <T>( value: ClassAccessorDecoratorTarget<T>, context: ClassAccessorDecoratorContext<ThisType<T>, T> ) => ClassAccessorDecoratorResult<ThisType<T>, T> | void;contextは、kind: 'getter'または'setter'、name、static、private、access、およびaddInitializerを提供します。 -
フィールドデコレーター: クラスフィールドに適用されます。
typescripttype FieldDecorator = <T>( value: undefined, // Field decorators receive undefined as value context: ClassFieldDecoratorContext<ThisType<T>, T> ) => ((initialValue: T) => T) | void;contextは、kind: 'field'、name、static、private、access、およびaddInitializerを提供します。戻り値は、フィールドの初期値を変更する関数です。 -
オートアクセサーデコレーター:
accessorプロパティに適用されます。typescripttype AutoAccessorDecorator = <T>( value: ClassAccessorDecoratorTarget<T>, context: ClassAccessorDecoratorContext<ThisType<T>, T> ) => ClassAccessorDecoratorResult<ThisType<T>, T> | void;ゲッター/セッターに似ていますが、結合された
accessor構文用です。
設定
ネイティブデコレーター用にtsconfig.jsonが設定されていることを確認してください。
{
"compilerOptions": {
"target": "ES2022", // Or higher
"module": "ESNext",
"lib": ["ES2022", "DOM"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "Bundler", // Or Node16/NodeNext
"experimentalDecorators": false, // Crucial: Disable legacy decorators
"emitDecoratorMetadata": false, // Crucial: Disable legacy metadata
"useDefineForClassFields": true // Recommended for class fields
}
}
ユースケース1:軽量な依存性注入コンテナ
ゼロ依存のDIコンテナは、サービスクラスのライフサイクル管理と依存性注入のために、クラスデコレーターとフィールドデコレーターを使用する例です。
// di.ts
// A simple map to store registered services and their instances
const serviceRegistry = new Map<string | symbol, { constructor: new (...args: any[]) => any; instance?: any; singleton: boolean }>();
// Symbol for internal metadata storage on classes
const INJECTABLE_METADATA = Symbol('injectable_metadata');
/**
* Class decorator to mark a class as injectable and register it with the DI container.
* @param singleton If true, only one instance of the class will be created and reused.
*/
export function Injectable(singleton: boolean = true) {
return <T extends new (...args: any[]) => any>(
target: T,
context: ClassDecoratorContext<T>
) => {
if (context.kind !== 'class') {
throw new Error(`@Injectable can only be applied to classes, not ${String(context.kind)}`);
}
const serviceName = target.name;
if (serviceRegistry.has(serviceName)) {
console.warn(`Service "${serviceName}" already registered. Overwriting.`);
}
serviceRegistry.set(serviceName, { constructor: target, singleton });
// Store metadata about dependencies for this class
context.addInitializer(function(this: T) {
// This runs after the class is defined, but before any instances are created.
// We can use 'this' to refer to the class constructor itself.
// Any @Inject() decorators would have added metadata to this.
});
return target; // Return the original class
};
}
/**
* Property decorator to inject a dependency into a class field.
* The field type annotation is used to determine the dependency to inject.
*/
export function Inject(targetService?: new (...args: any[]) => any) {
return <T, V>(
value: undefined, // Field decorators receive undefined as value
context: ClassFieldDecoratorContext<T, V>
) => {
if (context.kind !== 'field') {
throw new Error(`@Inject can only be applied to fields, not ${String(context.kind)}`);
}
// This initializer runs when an instance of the class is created.
context.addInitializer(function(this: T) {
// 'this' refers to the instance of the class being constructed.
// We need to determine the type of the field to inject.
// In TypeScript, runtime type information for fields is not directly available
// without emitDecoratorMetadata (which we're avoiding) or explicit type passing.
// For simplicity, we'll rely on `targetService` if provided, or assume the field name
// corresponds to a registered service name. A more robust solution might use a symbol
// or string literal for the service key.
const fieldName = String(context.name);
let serviceKey: string | symbol;
if (targetService) {
serviceKey = targetService.name;
} else {
// Fallback: assume field name is the service name.
// This is less robust and prone to naming conflicts.
// A better approach would be to require @Inject(ServiceClass)
serviceKey = fieldName.charAt(0).toUpperCase() + fieldName.slice(1); // e.g., 'logger' -> 'Logger'
console.warn(`@Inject on field '${fieldName}' without explicit service. Assuming service name '${String(serviceKey)}'. Consider @Inject(ServiceClass).`);
}
const serviceEntry = serviceRegistry.get(serviceKey);
if (!serviceEntry) {
throw new Error(`Service "${String(serviceKey)}" not found in DI container for injection into field "${fieldName}".`);
}
if (serviceEntry.singleton && serviceEntry.instance) {
// Reuse existing singleton instance
(this as any)[fieldName] = serviceEntry.instance;
} else {
// Create new instance, resolving its dependencies recursively
const instance = resolve(serviceEntry.constructor);
if (serviceEntry.singleton) {
serviceEntry.instance = instance; // Store for future reuse
}
(this as any)[fieldName] = instance;
}
});
// Field decorators return a function to modify the initial value.
// Since we're injecting, we don't need an initial value from the decorator.
return;
};
}
/**
* Resolves an instance of a service from the DI container.
* Handles recursive dependency resolution.
*/
export function resolve<T>(constructor: new (...args: any[]) => T): T {
const serviceName = constructor.name;
const serviceEntry = serviceRegistry.get(serviceName);
if (!serviceEntry) {
throw new Error(`Service "${serviceName}" not registered with DI container.`);
}
if (serviceEntry.singleton && serviceEntry.instance) {
return serviceEntry.instance as T;
}
// Recursively resolve constructor arguments
// This is a simplified approach. A real DI container would inspect constructor
// parameters for @Inject annotations or type metadata.
// For this example, we assume constructor args are also Injectable services
// or primitive types not managed by DI.
const dependencies: any[] = []; // In a real system, this would be populated by inspecting constructor params
const instance = new constructor(...dependencies);
// The @Inject field decorators will run their initializers *after* the constructor completes
// and before the instance is fully returned from `new constructor()`.
// This is handled by the JS engine's class construction process.
if (serviceEntry.singleton) {
serviceEntry.instance = instance;
}
return instance;
}
// --- Usage Example ---
interface ILogger {
log(message: string): void;
}
@Injectable(true)
class ConsoleLogger implements ILogger {
log(message: string): void {
console.log(`[ConsoleLogger] ${message}`);
}
}
@Injectable(false) // Not a singleton
class TransientService {
private id: number;
constructor() {
this.id = Math.random();
console.log(`TransientService instance created: ${this.id}`);
}
getId(): number {
return this.id;
}
}
@Injectable(true)
class UserService {
@Inject(ConsoleLogger) // Explicitly inject ConsoleLogger
private logger!: ILogger; // Use definite assignment assertion
@Inject() // Implicitly inject TransientService (by field name convention)
private transientService!: TransientService;
getUser(id: string): string {
this.logger.log(`Fetching user ${id}`);
return `User ${id} (Transient ID: ${this.transientService.getId()})`;
}
}
// Resolve the root service
const userService = resolve(UserService);
console.log(userService.getUser('123'));
const anotherUserService = resolve(UserService);
console.log(anotherUserService.getUser('456'));
// Verify singleton behavior for UserService and ConsoleLogger
console.log('userService === anotherUserService:', userService === anotherUserService); // Should be true
// Verify transient behavior for TransientService
// The transientService injected into userService and anotherUserService should be different instances
// This is because @Inject() creates a new instance for each injection point if the service is not a singleton.
// However, since UserService itself is a singleton, the same TransientService instance will be injected
// into the *same* UserService instance. To see different TransientService instances, we'd need to
// resolve UserService multiple times if it were *not* a singleton, or inject TransientService into
// different non-singleton services.
// Let's demonstrate by resolving TransientService directly:
const ts1 = resolve(TransientService);
const ts2 = resolve(TransientService);
console.log('ts1 === ts2:', ts1 === ts2); // Should be false, as TransientService is not a singleton
// Expected output:
// TransientService instance created: 0.xxxx
// TransientService instance created: 0.yyyy
// [ConsoleLogger] Fetching user 123
// User 123 (Transient ID: 0.xxxx)
// [ConsoleLogger] Fetching user 456
// User 456 (Transient ID: 0.xxxx)
// userService === anotherUserService: true
// TransientService instance created: 0.zzzz
// TransientService instance created: 0.aaaa
// ts1 === ts2: false
このDIコンテナは、@Injectableを使用してクラスを登録し、@Injectを使用して依存性注入の対象となるフィールドをマークします。フィールドデコレーター内のaddInitializerコールバックは非常に重要です。これはクラスコンストラクターの後、しかしインスタンスが完全に返される前に実行され、解決された依存性でフィールドを埋めることができます。
ユースケース2:高スループットなランタイムスキーマ検証
ランタイム検証は、API境界とデータ整合性にとって不可欠です。デコレーターは、スキーマの適用と検証を自動化できます。ここでは、簡略化された検証ライブラリを使用します。
// validator.ts
type ValidatorFn = (value: any) => string | null; // Returns error message or null if valid
const VALIDATION_METADATA = Symbol('validation_metadata');
interface FieldValidation {
propertyName: string | symbol;
validators: ValidatorFn[];
}
/**
* Stores validation rules on the class prototype.
*/
function addValidationRule(target: Object, propertyName: string | symbol, validator: ValidatorFn) {
if (!Reflect.hasOwnMetadata(VALIDATION_METADATA, target)) {
Reflect.defineMetadata(VALIDATION_METADATA, [], target);
}
const validations = Reflect.getOwnMetadata(VALIDATION_METADATA, target) as FieldValidation[];
let fieldValidation = validations.find(fv => fv.propertyName === propertyName);
if (!fieldValidation) {
fieldValidation = { propertyName, validators: [] };
validations.push(fieldValidation);
}
fieldValidation.validators.push(validator);
}
/**
* Decorator factory for 'required' validation.
*/
export function Required() {
return <T, V>(
value: undefined,
context: ClassFieldDecoratorContext<T, V>
) => {
if (context.kind !== 'field') {
throw new Error(`@Required can only be applied to fields, not ${String(context.kind)}`);
}
context.addInitializer(function(this: T) {
addValidationRule(Object.getPrototypeOf(this), context.name, (val) =>
val === null || val === undefined || (typeof val === 'string' && val.trim() === '')
? `${String(context.name)} is required.`
: null
);
});
};
}
/**
* Decorator factory for 'minLength' validation.
*/
export function MinLength(length: number) {
return <T, V extends string>(
value: undefined,
context: ClassFieldDecoratorContext<T, V>
) => {
if (context.kind !== 'field') {
throw new Error(`@MinLength can only be applied to fields, not ${String(context.kind)}`);
}
context.addInitializer(function(this: T) {
addValidationRule(Object.getPrototypeOf(this), context.name, (val) =>
typeof val === 'string' && val.length < length
? `${String(context.name)} must be at least ${length} characters long.`
: null
);
});
};
}
/**
* Decorator factory for 'max' validation.
*/
export function Max(maxValue: number) {
return <T, V extends number>(
value: undefined,
context: ClassFieldDecoratorContext<T, V>
) => {
if (context.kind !== 'field') {
throw new Error(`@Max can only be applied to fields, not ${String(context.kind)}`);
}
context.addInitializer(function(this: T) {
addValidationRule(Object.getPrototypeOf(this), context.name, (val) =>
typeof val === 'number' && val > maxValue
? `${String(context.name)} must be at most ${maxValue}.`
: null
);
});
};
}
/**
* Class decorator to enable validation for a class.
* It adds a `validate()` method to the class prototype.
*/
export function Validatable() {
return <T extends new (...args: any[]) => any>(
target: T,
context: ClassDecoratorContext<T>
) => {
if (context.kind !== 'class') {
throw new Error(`@Validatable can only be applied to classes, not ${String(context.kind)}`);
}
// Add a validate method to the class prototype
context.addInitializer(function(this: T) {
Object.defineProperty(this.prototype, 'validate', {
value: function(this: any): string[] {
const errors: string[] = [];
const validations = Reflect.getOwnMetadata(VALIDATION_METADATA, Object.getPrototypeOf(this)) as FieldValidation[] || [];
for (const fieldValidation of validations) {
const value = this[fieldValidation.propertyName];
for (const validator of fieldValidation.validators) {
const error = validator(value);
if (error) {
errors.push(error);
}
}
}
return errors;
},
writable: true,
configurable: true,
});
});
return target;
};
}
// Polyfill for Reflect.metadata if not available (e.g., in some environments or older TS versions)
// For native decorators, Reflect.metadata is not strictly required by the spec itself,
// but it's a common pattern for storing metadata.
// If you're using a bundler like Webpack/Rollup/ESBuild, ensure 'reflect-metadata' is imported once at the entry point.
// `import 'reflect-metadata';`
// For this example, we'll assume it's available or provide a minimal shim.
if (typeof Reflect === 'undefined' || !Reflect.hasOwnMetadata) {
console.warn("Reflect.metadata not found. Providing a minimal shim. For production, consider 'reflect-metadata' polyfill.");
const metadataMap = new WeakMap<object, Map<string | symbol, any>>();
(Reflect as any).defineMetadata = (key: string | symbol, value: any, target: object, propertyKey?: string | symbol) => {
let targetMetadata = metadataMap.get(target);
if (!targetMetadata) {
targetMetadata = new Map();
metadataMap.set(target, targetMetadata);
}
const metadataKey = propertyKey ? `${String(propertyKey)}:${String(key)}` : String(key);
targetMetadata.set(metadataKey, value);
};
(Reflect as any).getOwnMetadata = (key: string | symbol, target: object, propertyKey?: string | symbol) => {
const targetMetadata = metadataMap.get(target);
if (!targetMetadata) return undefined;
const metadataKey = propertyKey ? `${String(propertyKey)}:${String(key)}` : String(key);
return targetMetadata.get(metadataKey);
};
(Reflect as any).hasOwnMetadata = (key: string | symbol, target: object, propertyKey?: string | symbol) => {
const targetMetadata = metadataMap.get(target);
if (!targetMetadata) return false;
const metadataKey = propertyKey ? `${String(propertyKey)}:${String(key)}` : String(key);
return targetMetadata.has(metadataKey);
};
}
// --- Usage Example ---
@Validatable()
class Product {
@Required()
@MinLength(3)
name: string;
@Required()
@Max(9999)
price: number;
description?: string;
constructor(name: string, price: number, description?: string) {
this.name = name;
this.price = price;
this.description = description;
}
}
// Test cases
const product1 = new Product('Laptop', 1200);
const errors1 = (product1 as any).validate();
console.log('Product 1 errors:', errors1); // Expected: []
const product2 = new Product('', 50000);
const errors2 = (product2 as any).validate();
console.log('Product 2 errors:', errors2); // Expected: ["name is required.", "name must be at least 3 characters long.", "price must be at most 9999."]
const product3 = new Product('TV', 10000);
const errors3 = (product3 as any).validate();
console.log('Product 3 errors:', errors3); // Expected: ["price must be at most 9999."]
const product4 = new Product('A', 100);
const errors4 = (product4 as any).validate();
console.log('Product 4 errors:', errors4); // Expected: ["name must be at least 3 characters long."]
// Demonstrate that validate method is added to prototype
console.log('Product.prototype has validate method:', 'validate' in Product.prototype); // true
この検証システムは、フィールドデコレーター(@Required、@MinLength、@Max)を使用して、検証ルールをプロパティにアタッチします。次に、@Validatableクラスデコレーターが、validateメソッドをクラスプロトタイプに注入します。このメソッドは、収集されたルールを反復処理し、インスタンスのプロパティに対して実行します。フィールドデコレーターのaddInitializerは、インスタンスが作成される前に検証ルールがクラスプロトタイプに登録されることを保証し、validateメソッドをすぐに利用可能にします。検証ルールを保存するためにReflect.defineMetadataとReflect.getOwnMetadataを使用していることに注意してください。これにはreflect-metadataポリフィル(または提供されているシム)が必要です。
アーキテクチャ比較:Stage 3 Decorators vs. レガシーexperimentalDecorators
| 機能 | Stage 3 Decorators (TS 5.8+) | レガシーexperimentalDecorators (TS < 5.8) |
|---|---|---|
| 仕様ステータス | ECMAScript Stage 3 (最終段階に近い) | 非標準、実験的 |
tsconfig.json | experimentalDecorators: false, emitDecoratorMetadata: false | experimentalDecorators: true, emitDecoratorMetadata: true (DI用) |
| デコレーターコンテキスト | 豊富なClassDecoratorContext, ClassMethodDecoratorContextなど | 明示的なコンテキストオブジェクトなし; 引数はtarget, key, descriptor |
addInitializer | はい、クラス定義の後またはインスタンス構築の後にコードを実行できる | 直接的な同等物なし; 多くの場合、手動での静的プロパティ設定が必要 |
| フィールドデコレーター | undefinedをvalueとして受け取り、初期化子関数を返す | target, keyを受け取り、descriptor (アクセサーの場合) またはtargetを変更 |
| プライベートフィールド | accessを介してプライベートフィールドを装飾し、操作できる | プライベートフィールドを装飾できない |
Reflect.metadata | 仕様上は本質的に必須ではない; カスタムメタデータに使用 | 型リフレクション(例:DI用)に大きく依存 |
| ランタイム動作 | デコレーターはクラス定義時に実行される | デコレーターはクラス定義時に実行される |
| パフォーマンス | 一般的に最適化されており、JSエンジンランタイムの一部 | Reflect.metadataやポリフィルによりオーバーヘッドが発生する可能性 |
| 保守性 | 標準化されたAPI、長期的な安定性が向上 | APIは変更される可能性があり、予測しにくい |
本番環境での注意点とトラブルシューティング
-
experimentalDecoratorsがまだ有効になっている:- 症状: デコレーターが予期しない動作をする、またはTypeScript 5.8+でもデコレーター構文についてTypeScriptが警告する。
- 原因:
tsconfig.jsonに"experimentalDecorators": trueがまだ残っている可能性が高い。 - 修正:
"experimentalDecorators": falseと"emitDecoratorMetadata": falseを設定する。targetがES2022以上であり、moduleResolutionがBundlerまたはNodeNextであることを確認する。
-
Reflect.metadataが見つからない:- 症状: メタデータパターン(検証の例など)を使用すると、
Reflect.defineMetadata is not a functionのようなランタイムエラーが発生する。 - 原因:
reflect-metadataポリフィルがインポートされていないか、ランタイム環境で利用できない。ネイティブのStage 3デコレーターは、本質的にReflect.metadataを提供しない。 - 修正: アプリケーションのエントリポイントの最上部に
import 'reflect-metadata';を追加する。reflect-metadataパッケージがインストールされていることを確認する(npm install reflect-metadata)。
- 症状: メタデータパターン(検証の例など)を使用すると、
-
ランタイムでの型情報:
- 症状: DIで
ServiceAをclass MyClass { @Inject() serviceA: ServiceA; }に注入したいが、デコレーターがServiceAの型を判断できない。 - 原因: TypeScriptの型はコンパイル時に消去される。
emitDecoratorMetadata(Reflect.metadataを介して型情報を提供する)はレガシーデコレーター用であり、Stage 3とは互換性がない。 - 修正: デコレーターに型を明示的に渡す。例:
@Inject(ServiceA)。コンストラクターインジェクションの場合、コンストラクターのFunction.prototype.toString()を解析するか、ビルド時のステップで型情報を抽出する必要がある。
- 症状: DIで
-
デコレーターの実行順序:
- 症状: 複数のデコレーターが同じターゲットに適用されたときに予期しない動作をする。
- 原因: デコレーターは特定の順序で適用される。
- フィールド/メソッド/アクセサー: 上から下へ、次に下から上へ評価される。
- クラス: すべてのメンバーが装飾された後に適用される。
addInitializerコールバックは、追加された順序で実行される。
- 修正: 実行順序を理解する。デコレーターAがデコレーターBの出力に依存する場合、Bが最初に実行されるようにする。
addInitializerの場合、登録された順序で実行される。
-
thisコンテキストとaddInitializer:- 症状:
addInitializer内のthisが間違ったオブジェクトを参照する。 - 原因:
ClassDecoratorContextの場合、thisはクラスコンストラクターを参照する。ClassFieldDecoratorContext、ClassMethodDecoratorContextなどの場合、thisは構築中のインスタンスを参照する。 - 修正:
thisコンテキストに注意する。クラスレベルの操作(プロトタイプメソッドの追加など)の場合、Object.getPrototypeOf(this)またはthis.prototypeを使用する(thisがコンストラクターの場合)。インスタンスレベルの操作(フィールド値の設定など)の場合、thisはインスタンスを直接参照する。
- 症状:
よくある質問
-
Stage 3 DecoratorsをReact/Angular/Vueで使用できますか? はい、できます。最新のフレームワークは適応しています。Angular 17+はStage 3デコレーターを完全にサポートしています。ReactとVueは、コアコンポーネントにデコレーターを本質的に使用しませんが、ユーティリティクラスやサービスでそれらを利用できます。ビルドツール(Webpack、Viteなど)がそれらを処理するように設定されていることを確認してください(例:
2023-11モードで@babel/plugin-proposal-decoratorsを使用するBabel)。 -
Stage 3 Decoratorsは通常の関数よりも遅いですか? デコレーターはクラス定義時に実行され、インスタンス作成ごとにランタイムで実行されるわけではありません(インスタンス構築中に実行される
addInitializerコールバックを除く)。オーバーヘッドは最小限であり、ほとんどのアプリケーションでは通常無視できます。主なパフォーマンスへの影響は、デコレーターが何をするか(例:複雑なリフレクションや重い計算)に起因します。 -
デコレーターをデバッグするにはどうすればよいですか? デコレーター関数内に
debugger;ステートメントを配置します。これらはクラス定義中に実行されるため、クラスが定義されるときにデバッガーが一時停止します。addInitializerコールバックの場合、インスタンス作成中に一時停止します。 -
コンストラクターパラメーターを装飾できますか? 現在のStage 3 Decoratorの提案には、パラメーターデコレーターは含まれていません。これは
experimentalDecoratorsの機能でしたが、複雑さとランタイム型リフレクションに関する懸念から削除されました。コンストラクターパラメーターを使用したDIの場合、通常は依存関係を明示的に提供するか、ビルド時のステップで型情報を抽出する必要があります。 -
デコレーター引数の
valueとcontextの違いは何ですか?valueは装飾されるもの(例:メソッド関数、クラスコンストラクター)です。フィールドデコレーターの場合、メソッドのように初期の「値」がないため、valueはundefinedです。contextは、そのkind(クラス、メソッド、フィールド)、name、staticステータス、privateステータス、および重要なaddInitializer関数など、装飾された要素に関するメタデータを提供します。
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

TypeScriptだけでは不十分:Next.jsでZodを使ったエンドツーエンドの型安全性
TypeScriptがランタイムで消失する理由、APIやServer Actionの境界で静的型が機能しない理由、そしてZodがいかに確実なスキーマ検証と型推論を提供するのかを学びましょう。
Read more
React 19 Actions実践ガイド: useActionState, useOptimistic & Server Actionの回復性
React 19 Actionsの実践的な使用法を解説する包括的なガイド。useActionState, useOptimistic, Server Actionの回復性を本番環境レベルのアーキテクチャとコード例で紹介します。
Read more
2026年版ReactRouter究極ガイド
ReactRouterv6以降の機能、ネストされたルーティングアーキテクチャ、データローダー、最新のステート駆動型ナビゲーションの習得について、包括的に深く掘り下げて解説します。
Read more