如何在TypeScript中正确指定部分对象签名?

时间:2016-08-28 00:31:07

标签: typescript

TypeScript代码基于动态表单的Angular 2 Cookbook章节 - https://angular.io/docs/ts/latest/cookbook/dynamic-form.html

请观察:

问题-base.ts

export class QuestionBase<T>{
  value: T;
  key: string;
  label: string;
  required: boolean;
  order: number;
  controlType: string;
  constructor(options: {
    value?: T,
    key?: string,
    label?: string,
    required?: boolean,
    order?: number,
    controlType?: string
  } = {}) {
    this.value = options.value;
    this.key = options.key || '';
    this.label = options.label || '';
    this.required = !!options.required;
    this.order = options.order === undefined ? 1 : options.order;
    this.controlType = options.controlType || '';
  }
}

多答案-问题-base.ts

import {QuestionBase} from './question-base';

interface AnswerOption {
  key: string;
  value: string;
}

export class MultiAnswerQuestionBase<T> extends QuestionBase<T> {
  answerOptions: AnswerOption[];

  constructor(options: {answerOptions?: AnswerOption[]}) {
    super(options);
    this.answerOptions = options.answerOptions;
  }
}

问题-dropdown.ts

import {MultiAnswerQuestionBase} from './multi-answer-question-base';

export class DropdownQuestion extends MultiAnswerQuestionBase<string> {
  controlType = 'dropdown';
}

现在我试图像这样实例化DropdownQuestion

new DropdownQuestion({
    key: 'brave',
    label: 'Bravery Rating',
    order: 3,
    answerOptions: [
        {key: 'solid', value: 'Solid'},
        {key: 'great', value: 'Great'},
        {key: 'good', value: 'Good'},
        {key: 'unproven', value: 'Unproven'}
    ]
});

这里有关于字段键,标签和顺序的TypeScript投诉:

Error:(45, 17) TS2345: Argument of type '{ key: string; label: string; order: number; answerOptions: { key: string; value: string; }[]; }' is not assignable to parameter of type '{ answerOptions?: AnswerOption[]; }'.
 Object literal may only specify known properties, and 'key' does not exist in type '{ answerOptions?: AnswerOption[]; }'.

我理解这个错误的含义,我可以通过引入更多类并使我的代码更类似于C#或Java来解决它,或者只是抛弃所有类型并将其保留为JavaScript。我怀疑,这两种方式都不是TypeScript方式。

那么,TypeScript修复它的方法是什么?

2 个答案:

答案 0 :(得分:1)

你真的有几个选择。由于您正在处理对象文字,因此它受excess property checks的约束,这阻止您进行该调用。文档告诉你如何解决这个问题。

  1. 使用类型断言

    new DropdownQuestion({
        key: 'brave',
        label: 'Bravery Rating',
        order: 3,
        answerOptions: [
            {key: 'solid', value: 'Solid'},
            {key: 'great', value: 'Great'},
            {key: 'good', value: 'Good'},
            {key: 'unproven', value: 'Unproven'}
        ]
    } as {answerOptions?: AnswerOption[]});
    
  2. 将字符串索引签名添加到类型

    constructor(options: {answerOptions?: AnswerOption[], [propName: string]: any}) {
    
  3. 不要在呼叫站点使用对象字面值,将params放在单独的变量中

    let options = {
        key: 'brave',
        label: 'Bravery Rating',
        order: 3,
        answerOptions: [
            {key: 'solid', value: 'Solid'},
            {key: 'great', value: 'Great'},
            {key: 'good', value: 'Good'},
            {key: 'unproven', value: 'Unproven'}
        ]
    };
    new DropdownQuestion(options);
    
  4. 但说实话,我没有看到首先拥有所有这些额外属性的重点,它们甚至没有使用过。他们应该被删除。

    请记住,由于原始示例在类型注释中使用了{},因此它有效地接受了任何对象,但没有已知的命名属性。它使用索引器来访问安全的属性,因为所有对象的成员都可以通过索引访问。此处的多余属性检查不适用,因为注释中未显示任何命名属性。由于您现在更改了签名,因此您现在收到错误。

    如果您想完全理解该问题,请参阅GitHub上的issue 3755。如果要在不使用上述建议的情况下使其工作,则应遵循示例模式并使用索引器访问对象的成员。否则,请使用经过验证的解决方案并将其他属性添加到签名中,并取消“部分”签名。

答案 1 :(得分:0)

鉴于您在构造函数中定义了文字类型,最好将所有这些类型传递给子类。正如您所料,创建更多的类/接口,使其类似于C#/ Java。

  

对于介绍新选项的每个类,请创建包含特定数据的新选项类型。

&#13;
&#13;
export class QuestionBaseOptions<T> {
    value?: T;
    key?: string;
    label?: string;
    required?: boolean;
    order?: number;
    controlType?: string;
};

export class QuestionBase<T>{
  value: T;
  key: string;
  label: string;
  required: boolean;
  order: number;
  controlType: string;
  constructor(options: QuestionBaseOptions<T> = {}) {
    this.value = options.value;
    this.key = options.key || '';
    this.label = options.label || '';
    this.required = !!options.required;
    this.order = options.order === undefined ? 1 : options.order;
    this.controlType = options.controlType || '';
  }
}

interface AnswerOption {
  key: string;
  value: string;
}

export interface MultiAnswerQuestionBaseOptions<T> extends QuestionBaseOptions<T> {
    answerOptions?: AnswerOption[];
}

export class MultiAnswerQuestionBase<T> extends QuestionBase<T> {
  answerOptions: AnswerOption[];

  constructor(options: MultiAnswerQuestionBaseOptions<T>) {
    super(options);
    this.answerOptions = options.answerOptions;
  }
}

export class DropdownQuestion extends MultiAnswerQuestionBase<string> {
  controlType = 'dropdown';
}

new DropdownQuestion({
    key: 'brave',
    label: 'Bravery Rating',
    order: 3,
    answerOptions: [
        {key: 'solid', value: 'Solid'},
        {key: 'great', value: 'Great'},
        {key: 'good', value: 'Good'},
        {key: 'unproven', value: 'Unproven'}
    ]
});
&#13;
&#13;
&#13;