类型不能分配给条件类型

时间:2019-08-29 07:40:24

标签: javascript typescript enums conditional-types

我在操场上有一个TypeScript代码段。请在TypeScript playground或此处查看

enum MyTypes {
    FIRST = "FIRST",
    SECOND = "SECOND",
    THIRD = "THIRD"
}

type TFirst = {
    type: MyTypes.FIRST
    foo: string
}

type TSecond = {
    type: MyTypes.SECOND
    foo: string
}

type TThird = {
    type: MyTypes.THIRD
    bar: string
}

type TConditionalType<T> =
    T extends MyTypes.FIRST ? TFirst :
    T extends MyTypes.SECOND ? TSecond :
    T extends MyTypes.THIRD ? TThird :
    null

const getMyObjectBasedOnType = <T extends MyTypes>(type: T): TConditionalType<T> | null => {
    switch (type) {
        case MyTypes.FIRST: {
            return {
                type: MyTypes.FIRST,
                foo: 'test'
            }
        }
        default: {
            return null
        }
    }
}

const firstObject = getMyObjectBasedOnType(MyTypes.FIRST)
// firstObject is type of TFirst or null which is okay
if (firstObject) {
    firstObject.foo
}

有一个函数getMyObjectBasedOnType(type: T),该函数根据type参数返回条件类型的对象。这似乎可行,因为末尾的firstObject类型为TFirst | null。全部清除在这里。

我返回对象时,第31行中提到的函数内部存在TypeScript错误,这是我遇到的问题。我得到这个: Type '{ type: MyTypes.FIRST; foo: string; }' is not assignable to type 'TConditionalType<T>'.我无法弄清楚出了什么问题。据我了解,这是TFirst的对象,应该可以。为什么我会收到此错误?什么是正确的解决方法?

2 个答案:

答案 0 :(得分:1)

关于您的问题,它来自延迟的条件类型。查看打字稿文档:https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types。 (搜索conditional types are deferred到页面中的正确位置。)

有关此设计决策的简短讨论: https://github.com/Microsoft/TypeScript/issues/29939

最简单的解决方案是使用一个更宽松的单独实现签名,同时将公共签名保留为对调用者更有利的条件类型:

type TConditionalType<T> =
    T extends MyTypes.FIRST ? TFirst :
    T extends MyTypes.SECOND ? TSecond :
    T extends MyTypes.THIRD ? TThird :
    null

function getMyObjectBasedOnType<T extends MyTypes>(type: T): TConditionalType<T>; 
function getMyObjectBasedOnType(type: MyTypes): TFirst | TSecond | TThird | null {
  switch (type) {
    case MyTypes.FIRST: {
      return {
        type: MyTypes.FIRST,
        foo: "test"
      }; // nothing wrong here
    }
    case MyTypes.SECOND: {
      return {
        type: MyTypes.FIRST,
        foo: "test"
      }; // unfortunately it would work... The implementation is permissive
    }
    default: {
      return null;
    }
  }
}

const firstObject = getMyObjectBasedOnType(MyTypes.FIRST)
if (firstObject) {
    firstObject.foo; // it would work
    firstObject.bar; // it would fail

}

我仍在弄清楚如何使其与箭头功能配合使用。要了解这两者之间的区别,可以在这里参考:Proper use of const for defining functions in JavaScript

答案 1 :(得分:0)

Pierre-Louis's solution非常优雅且有据可查?

一种替代方法,更详细,但仍然有效:

  • 在类型TConditionalType中包装类型Prettify,以重构对象类型,迫使TypeScript编译器不要“推迟”。
  • 使用类型断言

?一点建议:避免命名类型T*TFirstTSecondTThirdTConditionalType)以将它们与通用类型约束区分开来 →在下面的代码中,我分别将它们分别命名为FirstSecondThirdMyTypesMapped

enum MyTypes {
    FIRST = "FIRST",
    SECOND = "SECOND",
    THIRD = "THIRD"
}

type First = {
    type: MyTypes.FIRST
    foo: string
}

type Second = {
    type: MyTypes.SECOND
    foo: string
}

type Third = {
    type: MyTypes.THIRD
    bar: string
}

type Prettify<T> =
    T extends infer Tbis ? { [K in keyof Tbis]: Tbis[K] } : never

type MyTypesMappedTmp<T extends MyTypes> =
    T extends MyTypes.FIRST ? First :
    T extends MyTypes.SECOND ? Second :
    T extends MyTypes.THIRD ? Third :
    never

type MyTypesMapped<T extends MyTypes> = Prettify<MyTypesMappedTmp<T>>

const getMyObjectBasedOnType = <T extends MyTypes>(type: T) => {
    switch (type) {
        case MyTypes.FIRST:
            return {
                type: MyTypes.FIRST,
                foo: 'test'
            } as MyTypesMapped<T>
        // ...
        default:
            return null
    }
}