TypeScript の判別共用体は、パターン マッチングを次のレベルに引き上げる強力な機能です。これらを使用すると、単純な switch ステートメントを超えた、複雑でタイプセーフな条件付きロジックを作成できます。私は最近のプロジェクトでこのテクニックを広範囲に使用しており、TypeScript での制御フローへのアプローチ方法が変わりました。
基本から始めましょう。識別共用体は、共通のプロパティを使用して異なるバリアントを区別するタイプです。簡単な例を次に示します:
type Shape = | { kind: 'circle'; radius: number } | { kind: 'rectangle'; width: number; height: number }
ここでの「kind」プロパティは判別式です。これにより、TypeScript はその値に基づいて、どの特定の形状を扱っているかを推測できるようになります。
それでは、これをパターン マッチングにどのように使用できるかを見てみましょう:
function getArea(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2 case 'rectangle': return shape.width * shape.height } }
これはすばらしいことですが、まだ始まりにすぎません。これをさらに進めることができます。
差別結合の最も強力な側面の 1 つは、網羅性チェックです。 TypeScript を使用すると、パターン マッチングで考えられるすべてのケースを確実に処理できます。新しいシェイプを結合に追加しましょう:
type Shape = | { kind: 'circle'; radius: number } | { kind: 'rectangle'; width: number; height: number } | { kind: 'triangle'; base: number; height: number } function getArea(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2 case 'rectangle': return shape.width * shape.height // TypeScript will now warn us that we're not handling the 'triangle' case } }
これをさらに堅牢にするために、エラーをスローするデフォルトのケースを追加して、新しいケースの処理をうっかり忘れることがないようにすることができます。
function assertNever(x: never): never { throw new Error("Unexpected object: " + x); } function getArea(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2 case 'rectangle': return shape.width * shape.height case 'triangle': return 0.5 * shape.base * shape.height default: return assertNever(shape) } }
ここで、getArea 関数を更新せずに新しい図形を追加すると、TypeScript によってコンパイル時エラーが発生します。
しかし、パターンマッチングではさらに前進することができます。ネストされたパターンを含むより複雑な例を見てみましょう。
信号機用の単純なステートマシンを構築していると想像してください。
type TrafficLightState = | { state: 'green' } | { state: 'yellow' } | { state: 'red' } | { state: 'flashing', color: 'yellow' | 'red' } function getNextState(current: TrafficLightState): TrafficLightState { switch (current.state) { case 'green': return { state: 'yellow' } case 'yellow': return { state: 'red' } case 'red': return { state: 'green' } case 'flashing': return current.color === 'yellow' ? { state: 'red' } : { state: 'flashing', color: 'yellow' } } }
ここでは、トップレベルの状態で一致するだけでなく、「点滅」状態にあるときにネストされたプロパティでも一致します。
ガードを使用して、パターン マッチングにさらに複雑な条件を追加することもできます。
type WeatherEvent = | { kind: 'temperature', celsius: number } | { kind: 'wind', speed: number } | { kind: 'precipitation', amount: number } function describeWeather(event: WeatherEvent): string { switch (event.kind) { case 'temperature': if (event.celsius > 30) return "It's hot!" if (event.celsius < 0) return "It's freezing!" return "The temperature is moderate." case 'wind': if (event.speed > 100) return "There's a hurricane!" if (event.speed > 50) return "It's very windy." return "There's a gentle breeze." case 'precipitation': if (event.amount > 100) return "It's pouring!" if (event.amount > 0) return "It's raining." return "It's dry." } }
このパターン マッチングのアプローチは switch ステートメントに限定されません。これを if-else チェーンで使用したり、より複雑なシナリオのオブジェクト リテラルでも使用したりできます。
type Action = | { type: 'INCREMENT' } | { type: 'DECREMENT' } | { type: 'RESET' } | { type: 'SET', payload: number } const reducer = (state: number, action: Action): number => ({ INCREMENT: () => state + 1, DECREMENT: () => state - 1, RESET: () => 0, SET: () => action.payload, }[action.type]())
このアプローチは、訪問者パターンを実装する場合に特に役立ちます。以下に、識別共用体を使用して単純な式評価器を実装する方法の例を示します。
type Expr = | { kind: 'number'; value: number } | { kind: 'add'; left: Expr; right: Expr } | { kind: 'multiply'; left: Expr; right: Expr } const evaluate = (expr: Expr): number => { switch (expr.kind) { case 'number': return expr.value case 'add': return evaluate(expr.left) + evaluate(expr.right) case 'multiply': return evaluate(expr.left) * evaluate(expr.right) } } const expr: Expr = { kind: 'add', left: { kind: 'number', value: 5 }, right: { kind: 'multiply', left: { kind: 'number', value: 3 }, right: { kind: 'number', value: 7 } } } console.log(evaluate(expr)) // Outputs: 26
このパターンにより、新しいタイプの式を使用して式システムを簡単に拡張でき、TypeScript により、評価関数ですべてのケースが確実に処理されます。
このアプローチの最も強力な側面の 1 つは、大規模で複雑な条件付きブロックを、より管理しやすく拡張可能な構造にリファクタリングできることです。より複雑な例を見てみましょう:
さまざまな種類の金融取引を処理するシステムを構築していると想像してください。
type Shape = | { kind: 'circle'; radius: number } | { kind: 'rectangle'; width: number; height: number }
この例では、TypeScript のマップされた型と条件付き型を使用して、各キーがトランザクションの種類に対応し、各値がその特定の種類のトランザクションを処理する関数であるタイプセーフ オブジェクトを作成しました。このアプローチにより、handleTransaction 関数のコア ロジックを変更することなく、新しいタイプのトランザクションを簡単に追加できます。
このパターンの利点は、タイプセーフであり、拡張可能であることです。新しいタイプのトランザクションを追加すると、TypeScript によって対応するプロセッサ関数の追加が強制されます。存在しないトランザクションの種類を処理しようとすると、コンパイル時エラーが発生します。
識別共用体を使用したこのパターン マッチング アプローチは、特に複雑なアプリケーションにおいて、より表現力豊かで、より安全で、自己文書化された TypeScript コードを実現できます。これにより、複雑なロジックを読みやすく保守しやすい方法で処理できるようになります。
アプリケーションが複雑になるにつれて、これらの技術の価値はますます高まります。これらにより、正しいだけでなく、理解しやすく変更しやすいコードを書くことができます。 TypeScript の型システムを最大限に活用することで、楽しく作業できる堅牢で柔軟なシステムを作成できます。
目標は、機能するコードを書くことだけではなく、その意図を明確に表現し、要件の変化に応じてエラーに強いコードを書くことであることに注意してください。識別結合を使用したパターン マッチングは、この目標を達成するための強力なツールです。
私の経験では、これらのパターンを採用すると、コードの品質と開発速度が大幅に向上しました。識別共用体と徹底的なパターン マッチングの観点から考えることに慣れるまでには時間がかかりますが、一度慣れてしまえば、明確でタイプ セーフな方法でコードを構造化するための新しい可能性が開かれることがわかります。
TypeScript の探索を続ける際には、これらのパターンを独自のコードに適用する機会を探すことをお勧めします。おそらく複雑な if-else チェーンを識別共用体にリファクタリングするなど、小規模から始めてください。このテクニックに慣れてくると、コードを簡素化し明確にするためにこのテクニックを適用できる場所がどんどん見えてくるようになります。
TypeScript の真の力は、エラーをキャッチする機能だけではなく、より優れた、より表現力豊かなコード構造に導く機能にあることを忘れないでください。識別共用体や徹底的なパターン マッチングなどのパターンを採用することで、正しいだけでなく、読みやすく保守しやすいコードを作成できます。
私たちの作品をぜひチェックしてください:
インベスターセントラル | スマートな暮らし | エポックとエコー | 不可解な謎 | ヒンドゥーヴァ | エリート開発者 | JS スクール
Tech Koala Insights | エポックズ&エコーズワールド | インベスター・セントラル・メディア | 不可解な謎 中 | 科学とエポックミディアム | 現代ヒンドゥーヴァ
以上がTypeScript のパターン マッチングをマスターする: コードのパワーと安全性を強化するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。