简体   繁体   中英

TypeScript interface optional parameter but not viewed as optional

I have an interface that has a property that is optional, but it is set within my constructor with default values and then overridden with values passed in from the first parameter of the constructor. If the properties are not set the it uses the defaults.

How can I get it so that the parameter is still optional when passed in to the constructor, but when used within the class it is seen as set. I don't want to get the following error:

Object is possibly 'undefined'.

TypeScript Playground

export interface IMain {
  req: string;
  prop?: ISecondary;
}

export interface ISecondary {
  a?: string;
  b?: string;
}
export class ABC {
  public main: Main;

  public constructor(main: Main) {
    this.main.prop = {
      a: "A",
      b: "B",
      ...main.prop
    };
  }

  public doSomething() {
    this.main.prop.a = "Z";
  }
}

new ABC({
  req: 'cat'
});

One approach would be to make the property non-optional, and then use a Partial<IMain> to make the argument's all properties optional:

export interface IMain {
  prop: ISecondary;
}

export interface ISecondary {
  a?: string;
  b?: string;
}

const ISecondaryDefaults: ISecondary = {
  a: "A",
  b: "B",
};

export class ABC {
  public main: IMain;

  public constructor(main: Partial<IMain>) {
    this.main = {
      ...main,
      prop: {
        ...ISecondaryDefaults,
        ...main.prop,
      },
    };
  }

  public doSomething() {
    this.main.prop.a = "Z";
  }
}

You can mark the property as required for the class and change the constructor to this:

export class ABC {
    public main: Required<IMain>;

    public constructor(main: IMain) {
        this.main = {
            ...main,
            prop: {
                a: "A",
                b: "B",
                ...main.prop
        }};
    }
}

If you have properties that should still be optional you have to be a bit more specific with your types. eg

type OptionalProps<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

export interface IMain {
    req: string;
    prop: ISecondary; // Required here
}

export class ABC {
    public main: IMain;

    public constructor(main: OptionalProps<IMain, 'prop'>) {
        this.main = {
            ...main,
            prop: {
                a: "A",
                b: "B",
                ...main.prop
        }};
    }
}

Sometimes you might just want to define separate interfaces if this gets too complicated. You can always extract the identical properties to a parent interface. eg

interface IMainRequired {
    req: string;
}
interface IMainOptional {
    prop: ISecondary;
}
export type IMain = IMainRequired & IMainOptional
export type IMainArg = IMainRequired & Partial<IMainOptional>

export class ABC {
    public main: IMain;

    public constructor(main: IMainArg) {
        this.main = {
            ...main,
            prop: {
                a: "A",
                b: "B",
                ...main.prop
        }};
    }
}

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