我有一个类型,它忽略了界面的一个键。我想声明一个函数签名,其中Typescript可以推断返回的是类型或接口:
interface SomeInterface {
a: number;
b: string;
c: boolean;
d: string;
}
type SomeTypeFromInterface = Omit<SomeInterface, 'd'>;
let test: SomeTypeFromInterface;
interface Config{
withD: boolean;
some: string;
other: string;
values: string;
}
function getObject(config: Config): SomeInterface | SomeTypeFromInterface {
if (config.withD) {
return {} as unknown as SomeInterface;
} else {
return {} as unknown as SomeTypeFromInterface;
}
}
const myConfig: Config = {
withD: true,
some: 'some',
other: 'other',
values: 'values',
}
const myObject = getObject(myConfig);
myObject.d // <-- should exist
当前typescript doesn't infer correctly the return type:
这可能吗?
我试图这样做:
type Function1 = (t1: Config) => { [D in keyof Config]: null extends Config[D] ? SomeTypeFromInterface : SomeInterface };
const getObject: Function1 = (config: Config)=> {
if (config.withD) {
return {} as unknown as SomeInterface;
} else {
return {} as unknown as SomeTypeFromInterface;
}
}
但是它不起作用。
编辑:
答案 0 :(得分:3)
在一些地方,您需要通过跟踪withD
是true
还是false
(而不是boolean
)来指导编译器,以及如何getObject()
的返回值取决于此值。
首先,您需要myConfig
的类型来取决于其withD
属性是true
还是false
;您不能将其类型注释为Config
,因为它的宽度足以表示两种情况。编译器会立即忘记const myConfig: Config = {...}
拥有withD
作为true
的情况。最简单的方法就是根本不使用类型注释。您无需说说myConfig
符合Config
。 TypeScript具有structural类型的系统;如果myConfig
符合Config
,您仍然可以将其传递给需要Config
的函数。
所以首先我们要这样做:
const myConfig = {
withD: true,
some: 'some',
other: 'other',
values: 'values',
}
让我们检查myConfig
的类型:
/* const myConfig: {
withD: boolean; // whoops
some: string;
other: string;
values: string;
} */
嗯,withD
仍被推断为boolean
。这将我们带到需要干预的下一个地方:
您希望在类型级别上区分true
和false
,这意味着true
和false
本身应被视为boolean
literal types。请注意,在TypeScript中,boolean
类型与联合类型true | false
等效。
当您编写类似{ withD: true }
的值时,编译器将首先将该属性解释为true
文字类型,但随后通常将其解释为widen the type to boolean
unless you tell it not to。
有多种方法可以告诉您不要这样做。从TypeScript 3.4开始,最简单的方法之一就是使用const
assertion。如果将as const
添加到文字值,则倾向于将其推断为尽可能窄。因此,让我们尝试一下:
const myConfig = {
withD: true as const,
some: 'some',
other: 'other',
values: 'values',
}
结果
/* const myConfig: {
withD: true;
some: string;
other: string;
values: string;
} */
太好了。请注意,您可以将as const
放置在其他位置(例如const myConfig = {...} as const
),也可以做其他类似{withD: true as true}
的操作,但这已经足够。
现在已知myConfig
具有true
的{{1}}值,因此我们必须修改withD
来解决这个问题。一种可行的方法是使用overloads。 (我只是提供重载解决方案;您也可以将generic函数与conditional types一起使用,但是重载可能更容易理解)
getObject()
为方便起见,我添加了interface YesConfig extends Config {
withD: true
}
interface NoConfig extends Config {
withD: false
}
// call signatures
function getObject(config: YesConfig): SomeInterface;
function getObject(config: NoConfig): SomeTypeFromInterface;
function getObject(config: Config): SomeInterface | SomeTypeFromInterface;
// implementation
function getObject(config: Config): SomeInterface | SomeTypeFromInterface {
if (config.withD) {
return {} as unknown as SomeInterface;
} else {
return {} as unknown as SomeTypeFromInterface;
}
}
和YesConfig
类型来表示NoConfig
中的差异,然后有三个呼叫签名。如果withD
为config
,则输出为YesConfig
。如果SomeInterface
为config
,则输出为NoConfig
。如果SomeTypeFromInterface
仅是config
(因此,如果编译器无法识别),则输出为两种类型的并集。而且实现签名必须能够处理所有调用签名。
现在,最后,您可以执行以下操作:
Config
它有效!编译器选择第一个调用签名,输出类型为const myObject = getObject(myConfig);
myObject.d // <-- no error!
。
为完整起见,让我们看看使用此方法的其他方式:
SomeInterface
这里,const anotherObject = getObject({ withD: false, other: "", some: "", values: "" });
// const anotherObject: Pick<SomeInterface, "a" | "b" | "c">
anotherObject.d; // error, as expected
的{{1}}中有anotherObject
,输出为withD
;所以我们使用了上面的第二个呼叫签名。请注意,我们不必使用false
,因为对象文字立即被传递给SomeTypeFromInterface
。编译器知道某些as const
调用签名关心getObject()
属性的文字类型,因此它不会将getObject()
扩展为withD
。我们只需要这样做,因为在使用配置之前,您将配置放到了一个单独的变量中,这时为时已晚,不能扩展类型。
最后
false
在这里,我们真的不知道boolean
是const thirdObject = getObject({ withD: Math.random() < 0.5, other: "", some: "", values: "" });
// const thirdObject: SomeInterface | Pick<SomeInterface, "a" | "b" | "c">
thirdObject.d; // still error, since it might not be there
还是withD
,因此编译器选择第三个调用签名,其中输出是一个并集。
哇!好的,希望对您有所帮助。祝你好运!