如何制作使用字符串文字的TypeScript字符串枚举并正确进行类型推断

ale*_*ird 4 typescript typescript2.0

我想要一个类型来描述一组字符串,以及一个带有键的对象,以便于访问所述字符串.

选项1(不提供正确的类型推断)

const TwoWords与所有键的值初始化类型断言TwoWords,然后DoSomething(TwoWords.Foo)编译,但预期在switch语句中的typeguard不起作用-类型word在默认情况下是不是never.

type TwoWords = 'foo' | 'bar';
const TwoWords = {
    Foo: 'foo' as TwoWords,
    Bar: 'bar' as TwoWords
};

function DoSomething(word: TwoWords) {
    switch (word) {
        case TwoWords.Foo:
            break;
        case TwoWords.Bar:
            break;
        default:
            let typeInferenceCheck: never = word; // Type '"foo"' is not assignable to type 'never'
    }
}

DoSomething(TwoWords.Foo);
DoSomething('bar');
Run Code Online (Sandbox Code Playgroud)

选项2(正确的类型推断,但过于冗长)

但是,如果我为每个TwoWords键的值使用字符串文字类型断言word,则默认情况下的类型never与我期望的类型相同.

type TwoWords = 'foo' | 'bar';
const TwoWords = {
    Foo: 'foo' as 'foo',
    Bar: 'bar' as 'bar'
};

function DoSomething(word: TwoWords) {
    switch (word) {
        case TwoWords.Foo:
            break;
        case TwoWords.Bar:
            break;
        default:
            let typeInferenceCheck: never = word; // OK
    }
}

DoSomething(TwoWords.Foo);
DoSomething('bar');
Run Code Online (Sandbox Code Playgroud)

在案件'foo''bar'长得多串(比方说,一个完整的句子),我不想重复他们-它太冗长.是否有另一种方法可以使用字符串键控枚举,从switch语句(或if/else链)中的类型推断透视图中按预期运行?

选项3(不能与字符串文字互换)

根据Madara Uchiha的回答,您可以使用TypeScript 2.4字符串枚举获得正确的类型推断(如选项2中所示),但这些不可与字符串文字互换.

DoSomething('bar'); // Type '"bar"' is not assignable to parameter of type 'TwoWords'
Run Code Online (Sandbox Code Playgroud)

(有关StringScript 2.4字符串枚举的字符串文字赋值,请参阅GitHub问题#15930)

标准

我正在寻找另一个允许我拥有的选项:

  1. 用于访问字符串文字的枚举式对象EnumLikeObject.Foo === 'foo'
  2. 一种类型,指示只允许枚举成员,是否:
    1. 字符串文字 - let thing: EnumLikeObject = 'foo'
    2. 枚举对象的属性 - let thing: EnumLikeObject = EnumLikeObject.Foo
  3. 枚举式对象和类型必须具有相同的名称
  4. 在枚举式对象和类型的声明中,没有字符串文字可以重复两次以上.如果你有一个解决方案,他们只能重复一次,甚至更好.(在这个问题中,当我谈到冗长时,这个标准主要是我所说的.)

异议和讨论

  • 问题#15930链接来自选项3关于字符串枚举和字符串文字说"这里的理性是如果你使用字符串枚举,你应该一直使用它们来确保安全重构,否则坚持使用文字类型"
    • 在我们的项目中,我们对一些xml数据使用解析库(我们不控制数据格式).这为我们提供了一个使用字符串文字类型的类型化对象,我们将其映射到使用这些字符串枚举的内部使用的对象.(因此需要关于字符串文字.)有时,我们从内部对象到生成xml的另一种方式工作,为了便于在这些情况下使用,我们想要枚举而不仅仅是文字类型.
    • 我可能会考虑更改该解析库的类型定义以使用我们的字符串枚举而不是文字,然后我可以删除字符串文字分配要求,但我宁愿避免这样做,因为该库不是我们的责任而且似乎相当hacky从外部库中使用我们的内部类型.
  • 问题#16389来自@ tycho的评论说"任何不是常数的字面值都会被预测会改变,但不是在类型中"这就是为什么你需要告诉编译器你是否知道变量实际上不会改变的原因超出某些界限的类型.编译器必须推断出最常规的类型以允许更改值.

Mad*_*iha 5

如果你想等待和忍受一点,

TypeScript 2.4为游戏领域带来了真正的字符串枚举:

enum TwoWords {
  Foo = 'foo',
  Bar = 'bar'
}

function DoSomething(word: TwoWords) {
    switch (word) {
        case TwoWords.Foo:
            break;
        case TwoWords.Bar:
            break;
        default:
            let typeCheck: never = word; // OK
    }
}
Run Code Online (Sandbox Code Playgroud)

这可以让你获得两全其美.


Arl*_*ler 2

我想我明白了。至少:

  • 我在代码中没有看到任何红色下划线。
  • DoSomething接受"foo""bar"
  • 在默认情况下,word 是无效的。

并满足您的标准

  1. TwoWords.Foo === 'foo'
  2. 该类型仅允许指定的文字值。
  3. 枚举样式的对象和类型具有相同的名称
  4. 字符串文字仅写入一次

言归正传:

const TwoWords = (function () {
    const Foo = 'foo';
    const Bar = 'bar';

    const ret = {
        Foo: Foo as typeof Foo,
        Bar: Bar as typeof Bar,
    };
    return ret;
})()
type TwoWords = typeof TwoWords[keyof typeof TwoWords];
Run Code Online (Sandbox Code Playgroud)

然后我突然灵光一现

namespace TwoWords2 {
    export const Foo = "foo";
    export const Bar = "bar";
}
type TwoWords2 = typeof TwoWords2[keyof typeof TwoWords2]
// didn't test this, not sure if it actually updates the 
// original object or just returns a frozen copy
Object.freeze(TwoWords2); 
Run Code Online (Sandbox Code Playgroud)

在我看来,这不是一个缺点,因为它仍然会在类型检查器和 VS Code 中引发错误,但TwoWords2.Bar = "five"实际上是有效的,因为命名空间被编译为一个简单的对象。但这就是打字稿的工作方式。显然,第一个代码也有这个问题,但它不会抛出类型错误,所以第二个代码更好,IMO。