在实现的接口中使prop成为可选prop

时间:2020-04-29 15:18:12

标签: typescript typescript-generics

假设我们有一个接口IFieldConfig

export interface IFieldInputConfig {
    type: 'string' | 'password';
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    default?: any;
}

export interface IFieldConfig extends IExtractConfig {
    id: string;
    form?: IFieldInputConfig;
}

并有一个使用该接口的对象

export const fieldCompany: IFieldConfig = {
    id: 'company',
    form: {
        type: 'string',
        default: 'Apple',
    },
}

当我想使用它时,TypeScript认为fieldCompany.form.default可能是未定义的,即使很明显它是已定义的。我了解,在声明之后,form道具 可以删除,因此TypeScript没错。

如何告诉TypeScript这是密封的/不会被修改?

很明显,我可以创建更多特定的接口,但是我不想在每种可选道具的组合中都使用一个接口。我敢肯定有一种更简单,更直接的方法

4 个答案:

答案 0 :(得分:4)

我可能会有点简单,但是跳出来的一个解决方案就是根本不说它是IFieldConfig

export const fieldCompany = {
  id: 'company',
  form: {
    type: 'string' as const, // <=== Note
    default: 'Apple',
  },
};

现在,fieldCompany.form.default的类型为string,而不是string | undefined。您需要在as const上使用type: 'string',因为否则它将被推断为string,而不是'string' | 'password'

该对象仍与IFieldConfig兼容;可行:

const x: IFieldConfig = fieldCompany;

On the playground

答案 1 :(得分:2)

通常来说,除非我想要使编译器“忘记”所分配值的详细信息,否则我不会为类型注释变量。只有联合类型的值会在赋值时变窄,并且IFieldConfig本身不是联合类型。因此,当您将值分配给类型IFieldConfig的变量时,不会通过控制流分析将变量的类型缩小到更具体的范围。有关更多信息,请参见microsoft/TypeScript#16976microsoft/TypeScript#27706

我在这里的建议(没有更多信息让我相信),不要使用fieldCompany的注释,而是使用const assertion来使推断的类型尽可能地窄:

const fieldCompany = {
  id: 'company',
  form: {
    type: 'string',
    default: 'Apple',
  },
} as const;

您可以根据需要进行调整(例如,可能只有type的{​​{1}}是form),具体取决于您打算允许更改的结构部分以及哪些部分应保持狭窄/固定状态。

由于TypeScript的类型系统是structural而不是标称类型,因此这不会阻止您将as const用作fieldCompany

IFieldConfig

好的,希望能有所帮助;祝你好运!

Playground link to code

答案 2 :(得分:0)

我在操场上尝试了您的代码,实际上它抱怨undefined的可能性。

但是为了您的急救,您有一些解决方法!

Non-null assertion operator

fieldCompany.form!.default

您还可以禁用严格的空检查

tsconfig.json

{
  "compilerOptions": {
    "strictNullchecks": false
  }
}

答案 3 :(得分:0)

您可以定义一个通用的DeepRequired来获得如下信息:

  interface IExtractConfig{}

  export interface IFieldInputConfig<L extends any = any> {
    type: 'string' | 'password';
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    default?: L;
  }

  export interface IFieldConfig<L extends any = any> extends IExtractConfig {
    id: string;
    form?: IFieldInputConfig<L>;
  }

  type DeepRequired<T> = Required<{
    [K in keyof T]: Required<DeepRequired<T[K]>>
  }>

  export const fieldCompany: DeepRequired<IFieldConfig<string>> = {
    id: 'company',
    form: {
        type: 'string',
        default: 'Apple',
    },
  }

Typescript对default:any字段不满意。因此我将其更改为通用参数。

现在您拥有fieldCompany.form.default"string"

  function user(fc:DeepRequired<IFieldConfig<string>>){
    //here         fc.form.default assumed to be "string" and not "string" | "undefined" 
  }

有时候,您必须具有带有可选属性的基本接口。但是从我的经验来看,最好使用所有属性作为强制属性。最近,您可以使用“局部”和“拾取”将原始形状切成碎片。