【问题标题】:TypeScript class generic constraintTypeScript 类通用约束
【发布时间】:2020-02-28 00:24:06
【问题描述】:

当约束一个类的泛型类型时,ts 抱怨泛型可以用不同的子类型实例化。任何人都知道这实际上意味着什么(以及如何处理它)?

复制(TypeScript 3.6.4):

interface BaseSchema {
  readonly id: string;
}

class Model<GenericSchema extends BaseSchema> {
  public test(): GenericSchema {
    return { id: '' };
  }
}

错误:

输入 '{ id: string; }' 不可分配给类型 'GenericSchema'。 '{ id:字符串; }' 可分配给类型的约束 'GenericSchema',但 'GenericSchema' 可以用 约束'BaseSchema'.ts(2322)的不同子类型

好像和https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html#strict-function-types有关

我可以使它与类型断言一起工作,但它似乎不正确:

interface BaseSchema {
  readonly id: string;
}

export class Model<GenericSchema extends BaseSchema> {
  public test2(): GenericSchema {
    return { id: '' } as GenericSchema;
  }
}

更新:下面的新示例。在示例中,我们在实例化 Model 时提供了一个通用的 MySchema,这期望 test() 方法返回 MySchema。

class Model<
  GenericSchema extends {
    readonly id: string;
  }
> {
  public test() {
    const dbResult: GenericSchema = { id: '' };
    return dbResult;
  }
}

interface MySchema {
  readonly id: string;
  readonly name: string;
}

const model = new Model<MySchema>();
const schema: MySchema = model.test();

这些都是人为的例子,真正的用例是访问数据库并返回我有类型注释的数据。本质上,MySchema 将代表数据库中的数据模型,所以我想将它作为一个泛型提供(因为 TS 不可能知道数据在数据库中的样子)。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    也许调整一些名称将有助于泛型的概念。您应该始终在泛型类型标识符前面加上 T 以明确它们是什么。 (在这种情况下我选择了TSchema,但你也可以只使用T

    它给出错误的原因是因为 TSchema 可以是 Universe 中扩展 BaseSchema 的任何东西。而{ id: '' } 绝对不能满足“一切”。

    interface BaseSchema {
      id: string;
    }
    
    class Model<TSchema extends BaseSchema> {
      public test() {
        const dbResult: TSchema = { id: '' };
        return dbResult;
      }
    }
    

    您不能只创建一个带有 ID 的对象并声称它是 T 类型,因为您可以创建任何扩展 BaseSchema 的接口/类。您可以做出的唯一假设是它扩展了BaseSchema


    如果您只是想要一种访问数据并以正确类型返回数据的方法,那么服务的结果(数据访问或 HTTP 或其他任何东西)应该返回一个类型化的值或任何可以转换的值。如果是这种情况,则直接转换没有任何问题,因为您可以假设它以正确的格式从数据库返回(只需确保在保存到数据库之前验证模型!)。

    简单示例:

    interface BaseSchema {
      readonly id: string;
    }
    
    class Model<T extends BaseSchema> {
    
      private svc: SomeService;
    
      public constructor(private table: string) {
      }
    
      public get(id: string): T {
        return this.svc.getItemFromTableWithId(this.table, id) as T;
      }
    
      public getAll(): T[] {
        return this.svc.getAllFromTable(this.table) as T[];
      }
    
    }
    
    interface BlogArticle extends BaseSchema {
      name: string;
      author: string;
      lastUpdated: Date;
      published: boolean;
    }
    
    const blogArticles = new Model<BlogArticle>('blog-articles');
    
    blogArticles.get(4); // Returns an instance of `BlogArticle`.
    blogArticles.getAll(); // Returns an instance of `BlogArticle[]`.
    
    

    【讨论】:

    • 感谢您的回复!这是我真正想做的一个例子github.com/DefinitelyTyped/DefinitelyTyped/issues/39358 Collection 类型采用通用模式。然后这个模式就是 collection.findOne() 的返回类型。
    • 欲了解更多详情,请参阅此代码框codesandbox.io/s/typesmongodb-336-type-error-onshs
    • @MikaelLirbank 感谢您向我指出这些。这绝对是 TypeScript 中的一个错误,太好了!在那种情况下,我认为来自代码框的第二种方法是理想的:return this.collection.findOne({ _id: id } as Schema); 在这种情况下,您至少可以强制它具有Schema 的属性。
    【解决方案2】:

    考虑以下场景:

    interface BaseSchema {
      readonly id: string;
    }
    
    class Model<GenericSchema extends BaseSchema> {
      public test(): GenericSchema {
        return { id: '' };
      }
    }
    
    interface MySchema extends BaseSchema {
       readonly name: string;
    }
    
    var model = new Model<MySchema>();
    var schema = model.test();
    

    test 是否返回 MySchema?那么没有name 属性,所以不,它返回BaseSchema。本质上,这就是错误试图以某种迂回的方式告诉您的内容。 test 的返回类型应该是 BaseSchema,因为它唯一的属性是 id,你不能保证你会返回它的任何特定子类型。

    输入 '{ id: string; }' 不可分配给类型 'GenericSchema'。

    没错,GenericSchema 可以有任意数量的属性,我们不知道它的形状。

    '{ id: 字符串; }' 可分配给 'GenericSchema' 类型的约束

    换句话说,{ id: string; }可以分配给BaseSchema

    但“GenericSchema”可以使用不同的约束“BaseSchema”子类型来实例化

    GenericSchema 可以是任何扩展 BaseSchema 的东西,我们如何保证返回我们不知道的类型?

    【讨论】:

    • 非常感谢您的回答。您的示例中从未使用过 MySchema,您的意思是 var model = new Model&lt;MySchema&gt;();
    • 知道了。嗯,我在这里遗漏了一些东西。在您的示例中,我期待 var model = new Model&lt;MySchema&gt;(); 抱怨,因为 MySchema 不包含必需的 id 字段。但它没有......
    • interface MySchema extends BaseSchema - 如果一个接口扩展另一个接口,它的行为就像一个扩展另一个类的类,即不需要重新定义属性,不像一个类实现一个接口。
    • 我刚刚用另一个例子更新了我的问题,这个例子/推理是否有意义?
    • Ryan Cavanaugh(TypeScript 团队开发负责人)twitter.com/SeaRyanC/status/1121995986862084096 的说明
    猜你喜欢
    • 1970-01-01
    • 2018-03-30
    • 2021-01-25
    • 2017-08-28
    • 1970-01-01
    • 1970-01-01
    • 2017-05-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多