【问题标题】:Typescript - Check an object has all interface propertiesTypescript - 检查对象是否具有所有接口属性
【发布时间】:2019-09-30 23:01:52
【问题描述】:

假设我有一些接口:

export interface MyDocument {
    id: string,
    collection: string[];
}

然后我创建一个新的(将现有的转换为该类型):

const workingDocument = <MyDocument>document;

最后,我有这个 if 语句块来检查它是否真的包含我在该接口中指定的所有内容:

if (!workingDocument.id) {
   throw new Error("Document missing `id`");
} else if (!workingDocument.collection) {
   throw new Error("Document missing `collection` array");
}

但是我似乎不喜欢这样,因为 if 语句可能会永远增长并且不太好维护。

有没有更好的办法?

谢谢。

【问题讨论】:

  • 我看到的 typescript 最严重的滥用是在构造函数中带有 this 的类:constructor(data: any) { Object.assign(this,data); } - 瞧 - 根据编译器,你传递给这个类的任何东西都会自动遵守该类的任何属性。 ...运行时是另一回事。 Typescript 应该在编译时发现潜在的问题,使用它来发挥它的优势。 如果你正在投射并且你不确定你在投射什么,并且最终写了 ifs 来检查,那么你做错了打字稿。
  • 如果接口上声明的所有属性都没有在它的具体类中定义,那么理想情况下打字稿转译器会抛出一个错误来做同样的事情。理想情况下,您不应使用 if..else 语句检查值是否存在。

标签: javascript typescript interface


【解决方案1】:

原答案

如果我理解正确,您是在要求运行时检查对象是否包含接口定义的所有属性。这对于单独的接口是不可能的,因为与接口关联的类型信息不会使其运行时;换句话说,该接口在我们运行 TypeScript 编译器时有用。

您可以做的是创建一个包含接口所有属性的模式。然后您可以遍历该模式以检查对象上是否存在所有属性。这是一个可能看起来如何的示例。我已将示例包装在 user-defined type guard 中。

export interface MyDocument {
    id: string,
    collection: string[];
}

const isMyDocument = (input: any): input is MyDocument => {

    const schema: Record<keyof MyDocument, string> = {
        id: 'string',
        collection: 'array'
    };

    const missingProperties = Object.keys(schema)
        .filter(key => input[key] === undefined)
        .map(key => key as keyof MyDocument)
        .map(key => new Error(`Document is missing ${key} ${schema[key]}`));

    // throw the errors if you choose

    return missingProperties.length === 0;
}

const obj = {};

if (isMyDocument(obj)) {
  // the compiler now knows that obj has all of its properties
  obj.collection;
} 

这是上面的代码in the TypeScript playground

在评论中回答问题

以下是您可以如何使用... 运算符来扩展架构。

interface ParentDocument { 
    id: string,
    collection: [],
}

interface ChildDocument extends ParentDocument { 
    name: string;
}

const schemaParent: Record<keyof ParentDocument, string> = {
    id: 'string',
    collection: 'array'
};

const schemaChild: Record<keyof ChildDocument, string> = {
    name: 'string',
    ...schemaParent,
};

【讨论】:

  • 我认为这是有道理的,但是说export interface MyDocument 还扩展了另一个接口,那么isMyDocument 中的schema 是否也需要列出所有这些属性?是否可以避免重新列出所有这些?
  • 是的。 schema 也需要在扩展接口中包含所有属性。您可以通过使用Object.assign(...){ ...obj } 传播标识符在适当的地方组合各种模式来解决这个问题。
  • 不完全明白 - { ...obj } 会去哪里?
  • @userMod2 我用{ ...obj }的例子编辑了这个问题。
  • 太好了 - 它有效。但我不知道为什么大声笑!为什么子界面中有name: string;。如果那不存在,那整个界面可以不被删除吗?
【解决方案2】:

如果您在内部创建/使用此 Document 类型,则可以使用类型/接口来断言它们的类型 - 为自己 - 而无需强制转换。

但是,如果文档来自您的 typescript 应用程序之外,您将需要进行某种形式的手动类型保护/检查(您希望避免的事情)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-11-24
    • 2013-12-26
    • 2020-11-14
    • 1970-01-01
    • 2014-05-08
    • 1970-01-01
    • 2021-06-16
    相关资源
    最近更新 更多