【问题标题】:TypeScript interface optional parameter but not viewed as optionalTypeScript 接口可选参数,但不被视为可选参数
【发布时间】:2020-07-07 15:46:26
【问题描述】:

我有一个接口,它的属性是可选的,但它在我的构造函数中设置了默认值,然后被从构造函数的第一个参数传入的值覆盖。如果未设置属性,则使用默认值。

我怎样才能得到它,以便参数在传递给构造函数时仍然是可选的,但在类中使用时,它被视为已设置。我不想得到以下错误:

对象可能是“未定义”。

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

【问题讨论】:

    标签: javascript typescript


    【解决方案1】:

    您可以将属性标记为类所需,并将构造函数更改为:

    export class ABC {
        public main: Required<IMain>;
    
        public constructor(main: IMain) {
            this.main = {
                ...main,
                prop: {
                    a: "A",
                    b: "B",
                    ...main.prop
            }};
        }
    }
    

    如果您的属性仍然是可选的,则必须对您的类型进行更具体的说明。例如

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

    如果这变得太复杂,有时您可能只想定义单独的接口。您始终可以将相同的属性提取到父界面。例如

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

    【讨论】:

    • 我最喜欢第一个选项,我遇到的唯一问题是ISecondary,在我真正的ISecondary 中,我有一个page?: number,当我尝试增加它时,现在也显示Object is possibly 'undefined'.
    • @GetOffMyLawn:如果你想要递归地需要所有东西,你可以定义这样一个类型。不确定这是否正确:type RecursiveRequired&lt;T&gt; = { [K in keyof T]-?: T[K] extends object ? RecursiveRequired&lt;T[K]&gt; : T[K] }
    • 这对我来说效果最好:type RecursiveRequired&lt;T&gt; = { [P in keyof T]-?: T[P] extends (infer U)[] ? RecursiveRequired&lt;U&gt;[] : T[P] extends (object | undefined) ? RecursiveRequired&lt;T[P]&gt; : T[P] }
    【解决方案2】:

    一种方法是将属性设为非可选,然后使用Partial&lt;IMain&gt; 将参数的所有属性设为可选:

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

    【讨论】:

    • 这样做的问题是,它会让一切都变成可选的吗?
    • 好吧,你没有告诉我们你那里还有其他房产。 :) 如果你只需要一些属性是可选的,你可以使用Omit&lt;T, 'optionalProp1' | 'optionalProp2'&gt; &amp; Partial&lt;Pick&lt;T, 'optionalProp1' | 'optionalProp2'&gt;&gt;(你当然可以别名为type PartiallyPartial&lt;T, K&gt; = Omit&lt;T, K&gt; &amp; Partial&lt;Pick&lt;T, K&gt;&gt;
    • 对不起,我在你发布答案后立即更新了帖子:)
    猜你喜欢
    • 2018-09-06
    • 2018-10-21
    • 2011-02-03
    • 2017-10-18
    • 2022-08-06
    • 2019-12-06
    • 1970-01-01
    • 2010-12-12
    • 2018-02-03
    相关资源
    最近更新 更多