Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.5k views
in Technique[技术] by (71.8m points)

Typescript conditional type missing properties

I can't wrap my head around why the following TypeScript code fails while seemingly everything should be ok:

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

I expect options.isSuper to be undefined | { isSuper: boolean } and options.notSuper to be undefined | { notSuper: boolean }

Instead Typescript removes these properties alltogether.

Problem is solved when changing to

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

But it seems unnecessary.

Playground


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

In Options<T> as you want it to return an object with the parameter isSuper or notSuper I added in an interface for both of them.

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

In Options<T>as it can either be one of the aforementioned interfaces I created a union type for it called TSuper.

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

In the function withOptions<T> I used the as keyword, which is a Type Assertion which tells the compiler to consider the object as another type than the type the compiler infers the object to be. In this case it is the union of both IisSuper and InotSuper which Options<T> can exist as.

As Typescript cannot guarantee the type of options at runtime as you want to access either notSuper or isSuper so you have to narrow down the scope using the in keyword for options to access the parameter in the type you want.

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

Final code:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...