问题
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 typePrettify
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