我在操场上有一个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
的对象,应该可以。为什么我会收到此错误?什么是正确的解决方法?
答案 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*
(TFirst
,TSecond
,TThird
,TConditionalType
)以将它们与通用类型约束区分开来
→在下面的代码中,我分别将它们分别命名为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
}
}