打字稿条件类型缺少属性

时间:2021-01-23 04:52:14

标签: typescript conditional-types

我无法理解为什么下面的 TypeScript 代码会失败,而看起来一切都应该没问题:

interface Base { basic: boolean };
interface Super { basic: boolean; extra: boolean };

type Options<T> = T extends Super ? { isSuper: boolean } : { notSuper: boolean }

const baseOptions: Options<{ basic: boolean }> = { notSuper: true };                 // ok
const superOptions: Options<{ basic: boolean; extra: boolean }> = { isSuper: true }; // ok

type MakeOptions = <T>() => Options<T>

function withOptions <T>(makeOptions: MakeOptions): void {
  const options = makeOptions<T>();

  console.log(options.notSuper); // Error: Property 'notSuper' does not exist on type 'Options<T>'.(2339)
  console.log(options.isSuper);  // Error: Property 'isSuper' does not exist on type 'Options<T>'.(2339)
}

我希望 options.isSuperundefined | { isSuper: boolean }options.notSuperundefined | { notSuper: boolean }

取而代之的是 Typescript 将这些属性全部删除。

改成问题解决

type Options<T> = T extends Super ? { isSuper: boolean; notSuper?: undefined } : { notSuper: boolean; isSuper?: undefined }

但似乎没有必要。

Playground

1 个答案:

答案 0 :(得分:1)

Options<T> 中,因为您希望它返回带有参数 isSupernotSuper 的对象,所以我为它们添加了一个接口。

interface IisSuper { isSuper: boolean }
interface InotSuper { notSuper: boolean }

Options<T> 中,因为它可以是上述接口之一,所以我为它创建了一个联合类型,称为 TSuper

type Options<T> = T extends Super ? IisSuper : InotSuper
type TSuper = IisSuper | InotSuper

在函数 withOptions<T> 中,我使用了 as 关键字,这是一个类型断言,它告诉编译器将对象视为另一种类型,而不是编译器推断出的对象类型。在这种情况下,它是 IisSuperInotSuper 的并集,Options<T> 可以作为其存在。

由于 Typescript 无法保证运行时 options 的类型,因为您想要访问 notSuperisSuper,因此您必须使用 in 关键字缩小范围options 以访问所需类型的参数。

function withOptions <T>(makeOptions: MakeOptions): void {
  const options = makeOptions<T>() as TSuper;
  if('notSuper' in options){
    console.log(options.notSuper);
  }
  else if('isSuper' in options){
    console.log(options.isSuper);
  } 
}

最终代码:

interface Base { basic: boolean };
interface Super { basic: boolean; extra: boolean };
interface IisSuper { isSuper: boolean }
interface InotSuper { notSuper: boolean }

type Options<T> = T extends Super ? IisSuper : InotSuper
type MakeOptions = <T>() => Options<T>
type TSuper = IisSuper | InotSuper

const baseOptions: Options<{ basic: boolean }> = { notSuper: true };                 
const superOptions: Options<{ basic: boolean; extra: boolean }> = { isSuper: true }; 

function withOptions <T>(makeOptions: MakeOptions): void {
  const options = makeOptions<T>() as TSuper;
  if('notSuper' in options){
    console.log(options.notSuper);
  }
  else if('isSuper' in options){
    console.log(options.isSuper);
  } 
}