【问题标题】:Typescript dynamically create interfaceTypescript 动态创建接口
【发布时间】:2017-08-19 12:03:48
【问题描述】:

我使用 simple-schema 在对象中定义 DB 模式:

{
   name: 'string',
   age: 'integer',
   ...
}

是否有可能从这个对象创建一个接口或类,所以我不必输入两次?

【问题讨论】:

    标签: javascript typescript


    【解决方案1】:

    您可以这样做,但除非您认为您可能正在更改架构,否则它可能会带来更多麻烦而不是值得。 TypeScript 没有内置的方法来以您想要的方式推断类型,因此您必须哄骗它这样做:


    首先,定义一种将文字名称 'string''integer' 映射到它们所代表的 TypeScript 类型的方法(大概分别是 stringnumber):

    type MapSchemaTypes = {
      string: string;
      integer: number;
      // others?
    }
    
    type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
      -readonly [K in keyof T]: MapSchemaTypes[T[K]]
    }
    

    现在,如果您可以像您指定的那样采用适当类型的架构对象,并从中获取关联的类型:

    const personSchema = {name: 'string', age: 'integer'}; 
    type Person = MapSchema<typeof personSchema>; // ERROR
    

    糟糕,问题在于personSchema 被推断为{name: string; age: string},而不是所需的{name: 'string'; age: 'integer'}。您可以使用类型注释来解决这个问题:

    const personSchema: { name: 'string', age: 'integer' } = { name: 'string', age: 'integer' }; 
    type Person = MapSchema<typeof personSchema>; // {name: string; age: number};
    

    但现在感觉就像是在重复自己。幸运的是,有一种方法可以强制它推断出正确的类型:

    function asSchema<T extends Record<string, keyof MapSchemaTypes>>(t: T): T {
      return t;
    }
    const personSchema = asSchema({ name: 'string', age: 'integer' }); // right type now
    type Person = MapSchema<typeof personSchema>; // {name: string; age: number};
    

    2020-06 更新:在更新的 TS 版本中,您可以使用 const assertion 来获得相同的结果:

    const personSchema = { name: 'string', age: 'integer' } as const;
    type Person = MapSchema<typeof personSchema>;
    

    这行得通!


    看到它在行动on the Typescript Playground。希望有帮助;祝你好运!

    【讨论】:

    • 有没有办法让它像这样工作? const personObj = { name: 'string', age: 'integer' } type Person = MapSchema&lt;typeof asSchema(personObj)&gt;; 我在封装这个方面非常不成功。
    • 可以使用新的 Typescript 4.1 改进这个答案吗? devblogs.microsoft.com/typescript/announcing-typescript-4-1我不确定,还是不熟悉高级 TS 功能。
    • @nstrelow 我不知道怎么做;当前版本非常小。如果我们要重新映射键和值,那么新的 as-clauses-in-mapped-types 会有所帮助,但我们没有。
    【解决方案2】:

    我认为您不能声明动态接口。但是,您可以为具有已知属性的对象创建type

    您可以创建一个将字符串文字映射到实际类型的对象,例如'integer' =&gt; number,但这与问题无关。我不知道您使用的是什么框架,但以下示例适用于外观相似的框架:Mongoose。

    users.js

    export const UserSchema = mongoose.Schema({
        name: String,
        value: Number
    });
    
    export const Users = mongoose.Model('users', UserSchema);
    
    export type User = { [K in keyof typeof UserSchema]: any } ;
    

    用法:

    import { User, Users } from './user';
    
    Users.find({}).exec((err: Error, res: User) => { ... })
    

    返回的结果应该与 UserSchema 具有相同的键,但所有值都映射到任何值,因为您仍然必须将字符串文字映射到类型。

    【讨论】:

    • 是否可以断言类型 (User) 包含 javascript 对象 (UserSchema) 的 每个 键?
    • 我想做一些类似于@jcalz MapSchema util 的东西,但我没有使用任何类型的真实数据库模式,我只有一个简单的字段名称对象及其默认值.我不喜欢使用 { [K in keyof Thing]: string } 语法,因为我想断言我的“模式”的 每个 键都是针对创建的类型强制执行的 - 我可以在这里回答我自己的问题:@987654322 @
    猜你喜欢
    • 2021-11-10
    • 2021-06-08
    • 1970-01-01
    • 1970-01-01
    • 2018-07-03
    • 2020-08-22
    • 2020-11-24
    • 2016-12-30
    相关资源
    最近更新 更多