简体   繁体   中英

Typescript - conditional types based on value in a for of list

So these are my types:

export type typeOne = {
  A: string;
  B: string;
};

export type typeTwo = {
  A: string;
  C: string;
};

export type Result = typeOne | typeTwo; //condition 

And this is the use case:

for (const item: Result of list) {
   const something = item.B ? 'B exsist' : item.C;
   return something;
}

It keeps returning error:

TS2339: Property 'B' does not exist on type 'Result'. Property 'B' does not exist on type 'typeTwo'.

and also for the other one:

TS2339: Property 'C' does not exist on type 'Result'. Property 'C' does not exist on type 'typeOne'.

Do you have any idea how can I fix this?

Note : The following error is also happening for the loop:

TS2483: The left-hand side of a 'for...of' statement cannot use a type annotation.

You can use an in type guard to discriminate a union of object types with unequal keys:

for (const item: Result of list) {
   const something = "B" in item ? 'B exsist' : item.C;
   return something;
}

Playground link

One of the nuances of TypeScript is that if you have a union type of objects, you can only access the fields that are common between them. It is common to use this technique to add a common field that can tell the two apart. For instance:

export type typeOne = {
  type: 'one';
  A: string;
  B: string;
};

export type typeTwo = {
  type: 'two'
  A: string;
  C: string;
};

export type Result = typeOne | typeTwo; //condition 

for (const item: Result of list) {
   const something = item.type === 'one' ? 'B exists' : item.C;
   return something;
}

In this case, type acts as a discriminator. Read more here: https://basarat.gitbook.io/typescript/type-system/discriminated-unions


Alternatively, you can create a custom type guard to differentiate between the two types. This involves a function that takes your union type and evaluates at runtime if the value is of a particular type. You can use a type assertion to access a non-shared field in the union:

function isTypeOne(result: Result): result is typeOne {
  return (result as typeOne).B !== undefined;
}

for (const item: Result of list) {
   const something = isTypeOne(item) ? 'B exists' : item.C;
   return something;
}

Here, if isTypeOne fails, typeOne can be safely eliminated from Result , thus the type is inferred as typeTwo .

Regarding the last error you mentioned:

"TS2483: The left-hand side of a 'for...of' statement cannot use a type annotation."

The Result type definition should be part of your declaration of list . That way, there is no need to specify that the left-hand side of the statement is a Result .

For example, assuming list is an array:

const list: Array<Result> = [{A: 'foo', B: 'bar'}, {A: 'foo', C: 'baz'}]
for (const item of list) {
  // Run code from other answers here
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM