web-dev-qa-db-ja.com

TypeScript列挙型のマップ

TypeScript列挙型をどのようにマップしますか?たとえば、文字列でこれを行うことができます:

let arr = [ 'Hello', 'Goodbye' ];

arr.map(v => {
  if (v === 'Hello') {
    return ':)';
  } else if (v === 'Goodbye') {
    return ':(';
  }
); // [ ':)', ':(' ]

もちろん、これは列挙型では機能しません。

enum MyEnum { Hello, Goodbye };

MyEnum.map(v => {
  if (v === MyEnum.Hello) {
    return ':)';
  } else if (v === MyEnum.Goodbye) {
    return ':(';
  }
}); // does not work

理想的には、これを一般化された方法で実行して、所有している列挙型を単純に取得し、マップ関数型情報を保持しながらに渡すことができます。使用法は次のようになります。

map(MyEnum, v => {
  if (v === MyEnum.Hello) {
    return ':)';
  } else if (v === MyEnum.Goodbye) {
    return ':(';
  }
}); // [ ':)', ':(' ]

私はこれを行う関数を取得するためにいじくり回してきましたが、ジェネリックを適切に取得するのに問題が続いています。

15
Braden Snell

これを解決する関数は非常に単純です。

// you can't use "enum" as a type, so use this.
type EnumType = { [s: number]: string };

function mapEnum (enumerable: EnumType, fn: Function): any[] {
    // get all the members of the enum
    let enumMembers: any[] = Object.keys(enumerable).map(key => enumerable[key]);

    // we are only interested in the numeric identifiers as these represent the values
    let enumValues: number[] = enumMembers.filter(v => typeof v === "number");

    // now map through the enum values
    return enumValues.map(m => fn(m));
}

ご覧のとおり、最初に列挙型のすべてのキーを取得する必要があります(MyEnum.Helloは実際には実行時に1です)。次に、それらをマップし、関数を渡します。

使い方も簡単です(例と同じですが、名前を変更しました)。

enum MyEnum { Hello, Goodbye };

let results = mapEnum(MyEnum, v => {
  if (v === MyEnum.Hello) {
    return ':)';
  } else if (v === MyEnum.Goodbye) {
    return ':(';
  }
});

console.log(results); // [ ':)', ':(' ]

列挙型を数値のみにフィルタリングする必要があるのは、列挙型のコンパイル方法が原因です。

あなたの列挙型は実際にこれにコンパイルされます:

var MyEnum;
(function (MyEnum) {
    MyEnum[MyEnum["Hello"] = 0] = "Hello";
    MyEnum[MyEnum["Goodbye"] = 1] = "Goodbye";
})(MyEnum || (MyEnum = {}));
;

ただし、"Hello""Goodbye"は実行時に使用できないため、これらには関心がありません。


また、関数の直前に面白いtypeステートメントがあります。これは、パラメーターをsomeParameter: enumとして入力できないため、number -> stringマップとして明示的に指定する必要があるためです。

8
James Monger

ts-enum-utilnpmgithub )を使用すると、簡単でタイプセーフ(ジェネリックを使用)で、数値の逆ルックアップエントリをスキップします。

import { $enum } from "ts-enum-util";

enum MyEnum { Hello, Goodbye };

$enum(MyEnum).map(v => {
    if (v === MyEnum.Hello) {
        return ':)';
    } else if (v === MyEnum.Goodbye) {
        return ':(';
    }
}); // produces [':(', ':)']

注:ts-enum-utilは常に、ソートされた列挙型キーの順序に基づいて反復し、すべての環境で一貫した順序を保証します。 Object.keys()には保証された順序がないため、クロスプラットフォームで保証された方法で列挙型を「定義された順序で」反復することは不可能です。 (更新:ts-enum-utilの新しいバージョンは、列挙型が定義された元の順序を保持するようになりました)

文字列列挙型を使用している場合は、それをts-string-visitornpmgithub )と組み合わせて、より一般的なタイプセーフコンパイラチェックを実行し、すべてを処理することを保証します。マップ関数の可能な列挙値:(更新:ts-enum-utilの新しいバージョンには、ts-string-visitorの機能が含まれ、数値列挙型でも機能するようになりました!)

import { $enum } from "ts-enum-util";
import { mapString } from "ts-string-visitor";

enum MyEnum { Hello = "HELLO", Goodbye = "GOODBYE" };

$enum(MyEnum).map(v => {
    // compiler error if you forget to handle a value, or if you
    // refactor the enum to have different values, etc.
    return mapString(v).with({
        [MyEnum.Hello]: ':)',
        [MyEnum.Goodby]: ':('
    });
}); // produces [':(', ':)']
1
Jeff Lau

私はそれを一般的とは呼びませんが、私はこれを何度も使用し、他の人にとっても便利になるでしょう:

type TMyEnum = ':)'|':(';
class MyEnum {
    static Hello: TMyEnum = ':)';
    static Goodbye: TMyEnum = ':(';
}
console.log(MyEnum.Hello); // :)
console.log(MyEnum.Goodbye); // :(

これで、マッピング関数は必要なく、期待どおりに機能しますが、列挙型ごとに同様のクラスを個別に作成する必要があります(とにかくそうするので問題にはなりません)。私が今考えられる唯一の欠点は、そのプロパティを反復できないことです。しかし、今までは問題ではなかったので、私はそれを必要としませんでした。また、必要に応じて静的配列をクラスに追加できます。

0
Manuel Fodor