TypeScript动态创建接口

38
我使用simple-schema在对象中定义数据库架构:
{
   name: 'string',
   age: 'integer',
   ...
}

有没有办法从这个对象创建一个接口或类,这样我就不必重复输入所有的内容了?

2个回答

57
你可以这样做,但是除非你认为可能会更改模式,否则这可能会带来更多麻烦。 TypeScript没有内置的推断类型的方式,以满足你所需的方式,因此你必须诱导它去实现:
首先,定义一种将文字名称“string”和“integer”映射到它们所代表的TypeScript类型(分别为字符串和数字)的方法:
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 断言 来获得相同的结果:

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

可以正常使用!


Typescript Playground上看到它的效果。希望这有所帮助,祝你好运!


1
有没有办法让它像这样工作?const personObj = { name: 'string', age: 'integer' } type Person = MapSchema;我一直很难封装它。 - Felipe Müller
这个答案能否使用新的Typescript 4.1进行改进呢? https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/ 我不确定,还不熟悉高级TS功能。 - nstrelow
1
@nstrelow 我不明白怎么做;当前版本已经非常简化了。如果我们需要重新映射键和值,那么新的“as”子句在映射类型中会有所帮助,但我们并没有这样做。 - jcalz
很酷。现在,如果它对一个深层对象进行递归... =) - Gnimmelf
这段代码之所以能够运作,是因为其中所有的键都是不同类型的。但是,如果你有两个字符串类型的键,根据我的测试,它就无法正常工作。 - Kat Lim Ruiz

2
我认为您不能声明动态接口。但是,您可以为具有已知属性的对象创建type
您可以创建将字符串文字映射到实际类型的对象,例如'integer' => 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)的 每个 键? - Devin Rhode
我想做一些类似于@jcalz的“MapSchema”工具的事情,但我没有使用任何真实数据库架构,我只有一个简单的字段名称和它们的默认值对象。我不喜欢使用{ [K in keyof Thing]: string }语法,因为我想断言我的“模式”的每个键都强制为创建的类型 - 这里我能够回答自己的问题:https://stackoverflow.com/questions/64778252/create-typescript-interface-based-on-a-const-defaultvalues-map/64778253#64778253 - Devin Rhode

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接