1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScript 5.x移行で遭遇!Decoratorとconst型パラメータのエラー解決

1
Posted at

TypeScript 5.x移行で遭遇!Decoratorとconst型パラメータのエラー解決

TypeScript 5.xへのバージョンアップを試みて、既存のDecoratorが動かなくなったり、型推論が期待と異なったりして「あれ、コンパイルエラーが増えたぞ?」と困惑していませんか?特にTypeScript 5.0で導入された標準Decoratorと**const型パラメータ**は、既存のプロジェクトに大きな影響を与える可能性があります。

この記事では、TypeScript 5.xへの移行時に直面しがちなDecoratorとconst型パラメータに関するエラーの具体的な解決策と、安全な導入手順を、コード例を交えて解説します。読み終える頃には、TypeScript 5.x環境での開発をスムーズに進めるための実践的な知識が身についているでしょう。

TypeScript 5.x移行の前提と環境

TypeScript 5.xへの移行を始める前に、基本的な環境と変更点を理解しておくことが重要です。ここでは、Node.jsのバージョン要件とtsconfig.jsonの推奨設定について解説します。

Node.jsの最小バージョン要件

TypeScript 5.0では、package.jsonでNode.jsの最小バージョンが12.20以降に指定されています。古いNode.jsバージョンを使用している場合は、まずNode.jsをアップグレードしてください。

# 現在のNode.jsバージョンを確認
node -v

# (必要であれば) Node.jsを最新LTSにアップグレード (nvmを使用する場合)
nvm install --lts
nvm use --lts

tsconfig.jsonの推奨設定

TypeScript 5.xの機能を最大限に活用し、新しい標準Decoratorを正しく動作させるためには、tsconfig.jsonの設定が重要です。特にtargetmodule、そしてmoduleResolutionオプションに注意しましょう。

{
  "compilerOptions": {
    "target": "es2022", // またはそれ以降 (es2022はDecoratorをサポートする最小ターゲット)
    "module": "esnext", // または "node16", "bundler" などプロジェクトに合わせた選択
    "lib": ["es2022", "dom"], // 使用するAPIに応じて適宜追加
    "strict": true, // 厳密な型チェックを推奨
    "esModuleInterop": true, // ES ModulesとCommonJSの相互運用性
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src",
    // ★重要: `--experimentalDecorators`は削除またはfalseに設定
    // "experimentalDecorators": false, // またはこの行を削除
    // ★重要: 新しい標準Decoratorはデフォルトで有効
    "emitDecoratorMetadata": true // Reflect-metadataを使用する場合
    // "moduleResolution": "bundler" // バンドラーを使用する場合に検討
  },
  "include": ["src/**/*.ts"]
}

ポイント:

  • target: es2022以上を設定することで、Decoratorの出力がECMAScript標準に準拠します。
  • module: esnextnode16bundlerなど、プロジェクトのモジュール解決戦略に合わせた値を設定します。
  • experimentalDecorators: このフラグは削除するか、falseに設定してください。 これがtrueのままだと、新しい標準Decoratorは動作しません。
  • emitDecoratorMetadata: reflect-metadataライブラリを使用する(例: NestJS, TypeORMなど)場合は、このオプションをtrueに設定します。

TypeScript 5.xにおけるDecoratorの変更点と実装

TypeScript 5.0でDecoratorがECMAScriptのStage 3プロポーザルに準拠した標準機能として導入されました。これにより、以前の実験的Decoratorとは挙動が大きく異なります。ここでは、新しい標準Decoratorの具体的な実装方法を解説します。

標準Decoratorの基本

新しい標準Decoratorは、--experimentalDecoratorsフラグなしで動作します。クラス、メソッド、アクセサー、プロパティ、オートアクセサーに適用できます。

メソッドDecoratorの例を通して、その構造を見てみましょう。

// src/decorators.ts
// ClassMethodDecoratorContext は TypeScript 5.0 以降で利用可能
function loggedMethod(originalMethod: Function, context: ClassMethodDecoratorContext) {
  // contextオブジェクトからDecoratorが適用されたメソッド名を取得
  const methodName = String(context.name);

  // Decoratorは元のメソッドの代わりに実行される新しい関数を返す
  return function (this: any, ...args: any[]) {
    console.log(`LOG: Entering method '${methodName}'.`);
    const result = originalMethod.apply(this, args); // 元のメソッドを実行
    console.log(`LOG: Exiting method '${methodName}'.`);
    return result;
  };
}

// src/person.ts
import { loggedMethod } from './decorators';

class Person {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  @loggedMethod
  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}

const p = new Person("Ray");
p.greet();

実行方法:

  1. npm install typescript
  2. tsc (コンパイル)
  3. node dist/person.js (実行)

実行結果:

LOG: Entering method 'greet'.
Hello, my name is Ray.
LOG: Exiting method 'greet'.

解説:

  • loggedMethodはDecorator関数です。
  • 引数としてoriginalMethod(Decoratorが適用された元のメソッド)とcontextClassMethodDecoratorContext型のオブジェクト)を受け取ります。
  • contextオブジェクトは、Decoratorが適用された要素に関するメタデータ(name, kindなど)を提供します。
  • Decorator関数は、元のメソッドの代わりに実行される新しい関数を返します。この新しい関数内で、元のメソッドを実行する前後にロジックを追加できます。

TypeScript 5.xにおけるconst型パラメータの活用

TypeScript 5.0で導入されたconst型パラメータは、関数に渡されるオブジェクトや配列のリテラル型を保持するのに役立ちます。これにより、呼び出し側でas constアサーションを毎回記述する手間を省き、より厳密な型推論を簡単に実現できます。

const型パラメータの具体的な利用例

以下のコードは、const型パラメータがどのようにリテラル型を保持するかを示しています。

// src/utils.ts
// 型パラメータ T に `const` 修飾子を付与
const getLangs = <const T extends { langs: readonly string[] }>(
  args: T,
): T['langs'] => {
  return args.langs;
};

// 呼び出し側で `as const` なしでリテラル型が推論される
const langs = getLangs({ langs: ['ja', 'en', 'fr'] });
// langs の型は readonly ['ja', 'en', 'fr'] と推論される
console.log(langs); // ['ja', 'en', 'fr']

// 変数として渡す場合は、変数の定義時に `as const` が必要
const obj = { langs: ['es', 'de'] };
const otherLangs = getLangs(obj);
// objを直接渡すと、objの型が{ langs: string[] }に推論されてしまうため、
// otherLangsの型は readonly string[] となる
console.log(otherLangs); // ['es', 'de']

// 正しくリテラル型を推論させるには、objの定義時にas constを使用する
const objAsConst = { langs: ['es', 'de'] } as const;
const otherLangsAsConst = getLangs(objAsConst);
// otherLangsAsConst の型は readonly ['es', 'de'] と推論される
console.log(otherLangsAsConst); // ['es', 'de']

実行方法:

  1. tsc (コンパイル)
  2. node dist/utils.js (実行)

解説:

  • getLangs関数の型パラメータTconst修飾子を付けることで、argsとして渡されたオブジェクトリテラルの型が、より厳密なリテラル型として推論されます。
  • langs変数の型はreadonly ['ja', 'en', 'fr']となり、as constを明示的に書かずに済んでいます。
  • ただし、関数に渡す前に変数に格納する場合、その変数の定義時にas constを付けないと、const型パラメータの恩恵を受けられず、型が広範に推論されてしまう点に注意が必要です。

TypeScript 5.x移行でよくあるエラーと解決策

TypeScript 5.xへの移行時には、特にDecoratorとconst型パラメータの変更により、いくつかの一般的なエラーに遭遇する可能性があります。ここでは、それらのエラーと具体的な解決策を解説します。

1. 古い実験的Decoratorと新しい標準Decoratorの混在によるエラー

ハマりどころ

tsconfig.json"experimentalDecorators": trueが残ったままだと、TypeScript 5.0以降で導入された新しい標準Decoratorの構文(例: context引数を使用するDecorator)を使用しても、コンパイルエラー(TS1241: Decorators are not valid here.など)が発生したり、期待通りに動作しなかったりします。これは、両者の型チェックルールと出力が異なるためです。

解決策

  • 新しいプロジェクトの場合: tsconfig.jsonから"experimentalDecorators": true削除します。これにより、標準Decoratorがデフォルトで有効になります。
  • 既存プロジェクトで互換性が必要な場合:
    • もし既存のコードやライブラリ(例: Angularの古いバージョン、TypeORMなど)が実験的Decoratorに強く依存している場合、それらのライブラリが標準Decoratorに対応するまでは、"experimentalDecorators": trueを維持する必要があります。ただし、この状態では新しい標準Decoratorは使用できません。
    • 可能であれば、既存の実験的Decoratorを標準Decoratorの仕様に合わせてリファクタリングすることを検討してください。

2. const型パラメータを使用しないことによる型推論の広範化

ハマりどころ

関数にオブジェクトリテラルを渡した際に、TypeScriptがプロパティの型をより一般的な型(例: 'bar'からstring)に推論してしまい、期待する厳密なリテラル型が得られないことがあります。これにより、後続の処理で型エラーが発生したり、型安全性が低下したりします。

// 例: const型パラメータがない場合
function getFirstItem<T extends readonly any[]>(arr: T): T[0] {
  return arr[0];
}

const item = getFirstItem(['a', 'b', 'c']);
// itemの型は string と推論されてしまう
// 期待する型は 'a'

解決策

  • 関数定義の型パラメータにconst修飾子を付与します。 これにより、関数内でas constと同様の推論がデフォルトで有効になり、呼び出し側で明示的にas constを記述する手間が省けます。

    // 解決策: const型パラメータを付与
    function getFirstItem<const T extends readonly any[]>(arr: T): T[0] {
      return arr[0];
    }
    
    const item = getFirstItem(['a', 'b', 'c']);
    // itemの型は 'a' と推論される!
    
  • 関数呼び出し時にas constアサーションを使用することも可能ですが、const型パラメータを使用する方がより簡潔で、API設計の意図を明確にできます。

3. Decoratorのcontextパラメータが未使用の場合のnoUnusedParametersエラー

ハマりどころ

TypeScript 5.0の標準Decoratorではcontextパラメータが導入されましたが、これを定義しているにもかかわらず、contextオブジェクトを使用しない場合、compilerOptions.noUnusedParameterstrueに設定されているとエラーが発生することがあります。

// tsconfig.json: "noUnusedParameters": true
function myDecorator(originalMethod: Function, context: ClassMethodDecoratorContext) {
  // context が使われていないため、TS6133: 'context' is declared but its value is never read. が発生
  return function (this: any, ...args: any[]) {
    return originalMethod.apply(this, args);
  };
}

解決策

  • contextパラメータを使用しない場合は、アンダースコア(_)を付けて未使用であることを明示します。これにより、noUnusedParametersエラーを回避できます。

    function myDecorator(originalMethod: Function, _context: ClassMethodDecoratorContext) {
      // _context は未使用でもエラーにならない
      return function (this: any, ...args: any[]) {
        return originalMethod.apply(this, args);
      };
    }
    
  • または、contextオブジェクトから必要なプロパティのみを分割代入して使用します。

  • tsconfig.json"noUnusedParameters": falseに設定することも可能ですが、これはコード品質の低下を招く可能性があるため、通常は推奨されません。

4. moduleResolutionの変更によるモジュール解決エラー

ハマりどころ

TypeScript 5.0でcompilerOptions.moduleResolutionbundlerが導入されたり、デフォルトの解決戦略が変更されたりしたことで、既存のプロジェクトでモジュールが見つからないというエラー(例: TS2307: Cannot find module '...')が発生する可能性があります。特に、ESM環境で相対パスのインポートにファイル拡張子が含まれていない場合などに顕著です。

解決策

  • tsconfig.jsoncompilerOptions.moduleResolutionをプロジェクトの構成に合わせて適切に設定します。

    • Node.js環境を直接ターゲットにする場合はnodenext
    • Webpack, Rollup, esbuildなどのバンドラーを使用する場合はbundlerを検討します。bundlerは、バンドラーがESMとCommonJSをどのように解決するかをより正確にモデル化します。
  • ESM環境では、相対パスのインポートにファイル拡張子(例: ./utils.js./types.d.ts)を含める必要があります。

    // CommonJS (旧来のNode.js) では不要だったが、ESMでは必要になる
    import { someFunction } from './my-module.js';
    import type { MyType } from './my-types.d.ts'; // .d.ts ファイルも拡張子が必要な場合がある
    

設計上のトレードオフとベストプラクティス

TypeScript 5.xのDecoratorとconst型パラメータを効果的に活用するためには、それぞれの設計上のトレードオフを理解し、適切なベストプラクティスに従うことが重要です。

Decoratorのトレードオフとベストプラクティス

トレードオフ

  • コードの簡潔さ vs 隠れた複雑性: Decoratorはコードを簡潔にし、横断的な関心事を分離するのに役立ちますが、その動作がコードから直接読み取れない場合があり、デバッグや理解を難しくする可能性があります。Decoratorの適用箇所と実際のロジックが離れるため、予期せぬ副作用を生む可能性もあります。
  • 標準化された動作 vs 既存ライブラリとの互換性: 新しい標準DecoratorはECMAScriptの将来の仕様に沿っていますが、既存のフレームワーク(Angularなど)が提供する実験的Decoratorとは互換性がありません。TypeScript 5.xへの移行の際には、使用しているライブラリの対応状況を必ず確認し、必要に応じて段階的な移行計画を立てる必要があります。

ベストプラクティス

  • 横断的関心事への適用: ロギング、バリデーション、認証、キャッシュ、計測など、複数のクラスやメソッドに共通して適用されるロジックにDecoratorを使用します。ビジネスロジックの複雑な部分には適用を避けるべきです。
  • Decoratorファクトリの活用: Decoratorに引数を渡したい場合は、Decoratorファクトリ(Decorator関数を返す関数)を使用します。これにより、Decoratorの再利用性と設定の柔軟性が向上します。
  • 型安全性の確保: Decoratorを記述する際は、ClassMethodDecoratorContextなどのTypeScriptが提供する型を活用し、厳密な型安全性を確保します。カスタムDecoratorの型定義も適切に行います。
  • テストの実施: Decoratorは実行時にコードの挙動を変更するため、単体テストや統合テストを十分に実施し、意図した通りに動作することを確認します。特に、Decoratorが適用されたメソッドの振る舞いが変わらないか、または期待通りに変わるかを検証します。

const型パラメータのトレードオフとベストプラクティス

トレードオフ

  • 厳密な型推論 vs 型の柔軟性: const型パラメータはより厳密なリテラル型推論を提供し、型安全性を高めますが、場合によっては意図的に型を広げたいケースでは、明示的な型アサーション(例: as string[])が必要になることがあります。

ベストプラクティス

  • リテラル型の保持: オブジェクトや配列のリテラル値を関数に渡し、そのリテラル型をそのまま保持したい場合に積極的に使用します。これにより、as constを呼び出し側で毎回記述する手間を省き、コードの可読性を向上させます。
  • API設計への活用: ライブラリや共通ヘルパー関数の設計において、より厳密な型推論を提供したい場合にconst型パラメータを導入することで、利用者がより型安全なコードを書けるようになります。特に、設定オブジェクトや定数リストを扱う関数で有効です。

移行全般のベストプラクティス

  • 段階的な移行: 大規模なコードベースをTypeScript 5.xに移行する場合、一度にすべてを変換するのではなく、allowJs: truestrict: falseを設定したtsconfig.jsonから始め、ファイル拡張子を.jsから.tsに変更し、小さな単位で段階的に移行を進めます。ユーティリティ関数や型定義から始め、エントリーポイントに近づくように作業します。
  • anyunknownの活用: 移行中は、一時的にanyunknown型を使用してコンパイルエラーを抑制し、徐々に厳密な型に置き換えていきます。unknownanyよりも型安全性が高いため、可能な限りunknownを使用します。
  • @ts-expect-errorの利用: // @ts-ignoreの代わりに// @ts-expect-errorを使用することで、問題が解決された際にエラーを報告させることができます。これにより、一時的な抑制が恒久的な見過ごしになるのを防ぎます。
  • 型定義ファイルの活用: 外部ライブラリの型定義がない場合は、@typesパッケージをインストールするか、global.d.tsファイルでモジュール宣言を追加して型情報を補います。
  • tsconfig.jsonの厳格化: 初期移行が安定したら、noImplicitAnystrictなどのコンパイラオプションを段階的に厳しく設定し、型安全性を高めます。
  • チームトレーニングとコードレビュー: チームメンバーがTypeScriptに慣れるための学習リソースを提供し、ペアプログラミングやコードレビューを通じて知識を共有します。特にDecoratorのような新しい概念は、チーム全体で理解を深めることが重要です。

まとめ

本記事では、TypeScript 5.xへの移行時に遭遇しがちなDecoratorとconst型パラメータに関するエラーと、その具体的な解決策を解説しました。

重要なポイントは以下の通りです。

  • Decorator: TypeScript 5.0以降ではECMAScript標準に準拠した新しいDecoratorが導入され、--experimentalDecoratorsは不要になります。tsconfig.jsonからこのフラグを削除し、新しいcontext引数を使用するDecoratorの記述方法を理解することが、TypeScript 5.x移行の鍵です。
  • const型パラメータ: 型パラメータにconst修飾子を付与することで、関数呼び出し時にas constを記述することなく、より厳密なリテラル型推論が可能になります。これにより、APIの型安全性が向上し、コードが簡潔になります。
  • エラー解決: experimentalDecoratorsの残存、contextパラメータの未使用、moduleResolutionの設定不備などが主なエラー原因です。これらに対して、tsconfig.jsonの調整やコードの修正で対応できます。

これらの知見を活用し、TypeScript 5.xへのスムーズな移行と、より堅牢で型安全な開発を実現してください。さらに詳細な情報は、TypeScript 5.0 Release Notesを参照することをお勧めします。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?