クラススクリプトにおけるクラスベースのenums:それらはトラブルの価値があるか?


JavaScriptの最もguling省略の一つはenumsの一流のサポートです.他の言語で時間を費やしている誰でも、これらの単純な構造の価値を知っていますthe enum . しかし、タイプスクリプトの実装はかなり基本的です-フードの下で、彼らはちょうどオブジェクトです、そして、これは2つの重要な痛み点を提示します.
問題1 :配列に変換する必要がある
ES 6が我々に与えたので、これはNitpickのように見えるかもしれませんObject.values — しかし、我々がenumsのために最も一般的なユースケースを考えるならば、反復のために一定の必要があります.リストまたはドロップダウンを設定する必要があるたびに迷惑なのですが、隠されたコストがあります:結果の型はもはやenum、しかし文字列ではありません.これはすぐにenumから直接取られた文字列の値は、enumを期待どこでも受け入れられない状況につながる.
enum Bah { ... };
const humbug = (bah: Bah) => {};
const bahValues = Object.values(Bah);

// Error: Type 'string' is not assignable to type 'Blah'
humbug(bahValues[0])
我々が上流に注釈しようとしても、問題は持続します.
// Error: Type 'string' is not assignable to type 'Bah'
const bahValues = Object.values<Bah>(Bah);
const bahValues: Bah[] = Object.values(Bah);
私たちの唯一のオプションは、強いタイプで働く目的を破って、我々のコードで役に立たない雑音を作成するキャストか主張することです.
問題2 : typescript enumsを拡張できません
インセンスPython or Java は、カスタムの属性とメソッドをenumに直接許可するクラスです.いくつかのコードの哲学者は、これは静的なリストとは何も意味しているenumsのエートスに反論すると主張しています.しかし、私の経験では、enumはアプリケーションの変化から隔離されず、静的ではない.すべてのアプリケーションが存在する可能性があるいくつかの一般的な要件を考えます.
  • 反復/表示の静的ソート順序を定義する
  • カスタムtoString ローカライズまたはビジネスロジック
  • 削除せずに値を廃止する
  • 静的部分集合
  • クラスベースのenumsは、それがenum自体でこれらの特徴を共存させるのを可能にします.クラスは、ここ数年の間に機能的な反応スタイルへのシフトにおいて流行から落ちたかもしれません.どのように我々はTypesScriptでこれを達成するか?

    クラススクリプトにおけるクラスベースのenumの記述
    まず、コードから始めましょう.
    export class Priority {
      static asArray: Priority[] = [];
    
      // Values
      static readonly CRITICAL = new Priority('CRITICAL');
      static readonly HIGH = new Priority('HIGH');
      static readonly MODERATE = new Priority('MODERATE');
      static readonly MEDIUM = new Priority('MEDIUM', true);
      static readonly LOW = new Priority('LOW');'
    
      // Subsets
      static readonly GENERATES_WARNINGS = [
        Priority.CRITICAL,
        Priority.HIGH,
      ];
    
      static readonly ACTIVE = Priority.asArray
        .filter(({ deprecated }) => !deprecated);
    
      constructor(
        public readonly value: string,
        public readonly deprecated = false,
      ) {
        Priority.asArray.push(this);
      }
    
      valueOf() {
        return this.value;
      }
    
      toString() {
        return someLocalizationFunction(this.valueOf());
      }
    
      get order() {
        return Priority.asArray.indexOf(this);
      }
    }
    
    まず、静的コレクションを定義しますasArray , 値を追加する前にインスタンス化する必要があります.次に、優先度enumを作成します.注意するMEDIUMfalse 自分自身を指定するdeprecated . コンストラクタを見てみるとdeprecated は他のenumに対してfalseに設定され、それぞれの新しい優先度が静的に加えられるasArray コレクション.個々の値が作成された後、手動で、またはenumの他のプロパティを使用して値の任意のサブセットを作成できます.
    最後に、我々のアクセサがあります.使用valueOf() and toString() ECMAScriptのオブジェクトと文字列との一貫したインターフェイスを提供します.我々のオーダーゲッターのために、私たちは値自身の定義順序に依存することができますasArray ), ソート順序を定義する簡単なメカニズムを提供します.
    これにより、私たちが新しいENUMクラスを使い始める必要があるのです.
    class ErrorMessage {
      constructor(public priority: Priority) {}
    }
    
    const criticalMessage = new ErrorMessage(Priority.CRITICAL);
    const allErrors = Priority.asArray.map(ErrorMessage);
    const warnings = Priority.GENERATES_WARNINGS.map(ErrorMessage);
    
    これは素晴らしい!我々は多くの一般的なユースケースと保存型安全性のために解決しました.しかし、これはすべての努力の価値がありますか?

    クラスベースのエンスは重要な欠点を持っている
    いくつかの問題があります.
    すぐに我々はより多くのenumsを作成を開始すると、我々は自分自身が一般的な操作を要因としようとすると見つけるでしょう-しかし、これは挑戦することを証明します.ENUMクラスを作り、いくつかの機能を動かすことができましたtoString() and valueOf() . しかし、すべての静的メンバーは、各enumに固有であり、離れて抽象化することはできません.また、ジェネリックを使用する必要がありますが、静的メンバーにジェネリックを適用することはできません.最後の結果は、いくつかの巧妙な抽象化によっても、それぞれの新しいenumで重複したコードがたくさんあるということです.
    もう一つの問題は、これらのenumsがインスタンス化を必要とするということです.外部ソースから生データを入力している場合は、次のようにします.
    interface PrioritizedError {
      error: {
        priority: Priority
      }
    }
    
    const errorData: PrioritizedError = {
      error: {
        priority: 'CRITICAL' // Invalid type
      }
    }
    
    我々は注釈できないerrorData 我々とPrioritizedError インターフェイスです.我々はまず、このデータを確実にするためにerror.priority 優先度enumでインスタンス化されます.
    const originalData = require('error.json');
    const transformedData: ExternalError = {
      error: {
        priority: Priority[originalData.error.priority],
      }
    };
    
    これは、元のデータとアプリケーションによって使用されるデータとの間のギャップを作成します.我々は逆問題に直面しています.外部ソースにデータを送信しているかもしれません.これは、継ぎ目がないかもしれないパイプラインの追加の層を導入します.我々がデータに触れるたびにバグや腐敗の別の機会です.
    この変換の問題は、ファイルの読み込み/書き込みまたはAPIリクエストに対して孤立していない.第三者図書館は我々のenumを受け入れません、したがって、我々は個々の構成要素の範囲内で前後に変化しなければならないかもしれません.外部の依存関係が予想された形式でデータを提供することができなかったとき、外部の依存関係が警告しないかもしれません.
    だから、クラスベースのenums努力の価値がある?ほとんどのことと同様に、私は答えがロック固体であると思います.
    これらの実装は確かに最適ではありません.私は、より多くの改良が可能であると確信しています.これらの改善のいくつかは適切にスケーラビリティ/乾いた問題に対処するかもしれません.それでも、決定は、主にあなたのアプリケーションのニーズに降ります.
    あなたのenumsのいくつかが緊密に結合されたビジネスロジックで来る傾向があるか、柔軟に追加のプロパティとメタデータをサポートする構造を必要とするとわかるならば、これは有用なパターンであるかもしれません.しかし、簡単な反復を必要として、どんなカスタムメソッドも必要としないならば、クラスenumは多分overkillです.新しい変換を加える必要がある状況では、私は特に注意を払います.