Type is not assignable to conditional type

◇◆丶佛笑我妖孽 提交于 2021-02-10 03:26:23

问题


I have a TypeScript code snippet in the playground. Please take a look there at TypeScript playground or here:

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
}

There is a function getMyObjectBasedOnType(type: T) which returns an object of a conditional type based on the type parameter. This seems to work since firstObject at the end is of type TFirst | null. All clear here.

Problem which I have is TypeScript error inside mentioned function on line 31 when I am returning object. I get this: Type '{ type: MyTypes.FIRST; foo: string; }' is not assignable to type 'TConditionalType<T>'. I can't figure it out what is wrong. As far as I understand it that is an object of TFirst which should be okay. Why do I receive this error and what is proper fix for it?


回答1:


Regarding your issue it comes from conditional types that are deferred. Look at the typescript documentation: https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types. (search for conditional types are deferred to get to the right place in the page).

There were some brief discussions about this design decision: https://github.com/Microsoft/TypeScript/issues/29939.

The simplest solution is to use a separate implementation signature that is more permissive, while keeping the public signature with conditional types that is better for the caller:

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

}

I'm still figuring out how to make it works with arrow functions. To know the difference between those two you can refer here: Proper use of const for defining functions in JavaScript




回答2:


Pierre-Louis's solution is very elegant and documented 👍

An alternative, more verbose but still working:

  • Wrapping type TConditionalType in the type Prettify which reconstruct the object type, forcing the TypeScript compiler not to "defer".
  • Use type assertions

💡 Little advice: avoid naming types T* (TFirst, TSecond, TThird, TConditionalType) to differentiate them from generic type constraint → In the code beneath, I've named them respectively First, Second, Third, MyTypesMapped:

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
    }
}


来源:https://stackoverflow.com/questions/57705340/type-is-not-assignable-to-conditional-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!