あれこれ試した結果、 const
インスタンスを作って Mapped Types で型付けする方法に落ち着きました。
export type StringEnumAdapter<T> = { readonly [K in keyof T]: keyof T };
const FooKeyRaw = {
ITEM_1: 'ITEM_1',
ITEM_2: 'ITEM_2',
ITEM_3: 'ITEM_31', // こういうミスは通ってしまう
};
export type FooKeyType = keyof typeof FooKeyRaw;
export const FooKey = FooKeyRaw as StringEnumAdapter<typeof FooKeyRaw>;
// これは (TypeScript 2.5.3 では) NG
const aKey: FooKeyType = FooKeyRaw.ITEM_1;
// これは OK
const bKey: FooKeyType = FooKey.ITEM_1;
console.log(bKey);
// これは NG
const cKey: FooKeyType = FooKey.NOT_EXIST;
// これも NG
FooKey.ITEM_1 = 'ITEM_2';
// ITEM_31
console.log(FooKey.ITEM_3);
その他の方法
namespace + export const
TypeScriptでのイベント名を管理・指定するもう一つの方法(+ストリングリテラル型にも対応)
https://qiita.com/ConquestArrow/items/02826db3ddbe98d280bd
class の static メンバ
TypeScriptでのイベント名をclickで指定するのはもう辞めよう
https://qiita.com/tonkotsuboy_com/items/b32801246ec14f444514
const でキーだけ宣言して関数で初期化
typo 等でキーと値が乖離するミスは防げます。が、2017-10-15 現在、uglifyjs-webpack-plugin を通してもコンパイル時解決されないので実行コストが...
declare global {
interface ObjectConstructor {
keys<T>(t: T): Array<keyof T>;
}
}
export type StringEnumAdapter<T> = { readonly [K in keyof T]: keyof T };
type StringEnumAdapterMutable<T> = { [K in keyof T]: keyof T };
export function asStringEnum<T>(t: T): StringEnumAdapter<T> {
const tt = t as any as StringEnumAdapterMutable<T>;
for (const k of Object.keys(tt)) {
tt[k] = k;
}
return tt;
}
const BarKeyRaw = {
BAZ: null,
CUX: null,
};
export type BarKeyType = keyof typeof BarKeyRaw;
export const BarKey = asStringEnum(BarKeyRaw);
// これは OK
const aaKey: BarKeyType = BarKey.BAZ;
// BAZ
console.log(aaKey);
// これはNG
const bbKey: BarKeyType = 'BAZOOKA';
// これもNG
BarKey.BAZ = BarKey.CUX;