简体   繁体   中英

Destructured arguments under optional function arguments in typescript throw error

interface Type1 {
  attr1: string;
  attr2: string;
}
interface Type2 {
  attr1: string;
  attr2: string;
  attr3: string; // extra attribute
}

function fn(config: Type1 | Type2): void {
  // Property 'attr3' does not exist on type 'Type1 | Type2'.ts(2339)
  const { attr1, attr2, attr3 } = config;
  console.log(attr1);
  console.log(attr2);
  console.log(attr3);
}

Error code show before. And I know there is a solution that add optional attribute in attr3 . But as far as I'm concerned this solution is not good. Because as matter of fact there only exist 2 situations either Type1 or Type2 . In a word, optional way is not readability. How can I fix it in advanced way?

You could just simply check if attr3 exists in the object

function fn(config: Type1 | Type2): void {
  if ('attr3' in config) {
    const {
      attr1,
      attr2,
      attr3
    } = config;
  } else {
    const {
      attr1,
      attr2
    } = config;
  }
}

Or you could use a custom type guard

function IsType2(config: Type1 | Type2): config is Type2 {
  return (config as Type2).attr3 !== undefined;
}

function fn(config: Type1 | Type2): void {
  if (IsType2(config)) {
    const {
      attr1,
      attr2,
      attr3
    } = config;
  } else {
    const {
      attr1,
      attr2
    } = config;
  }
}

Or if we really wanted to destructure only once, we could create a join type, though we would be coercing the type rather than inferring it.

function fn(config: Type1 | Type2): void {
  const {
    attr1,
    attr2,
    attr3
  } = config as Type1 & Type2;
}

As far as I know, there's no way to directly destructure a union type

If you want to get the same result when missing attr3 attribute as with js code you can just cast config type to intersection of types:

interface Type1 {
  attr1: string;
  attr2: string;
}
interface Type2 {
  attr1: string;
  attr2: string;
  attr3: string; // extra attribute
}

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

function fn(config: Type1 | Type2): void {
  // Property 'attr3' does not exist on type 'Type1 | Type2'.ts(2339)
  const { attr1, attr2, attr3 } = config as UnionToIntersection<Parameters<typeof fn>[0]>;
  console.log(attr1);
  console.log(attr2);
  console.log(attr3);
}

fn({ attr1: '', attr2: '' })

or just to ... = config as Type1 & Type2; if you don't mind some inflexibility.

TS playground

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