简体   繁体   English

TypeScript 循环/递归接口定义

[英]TypeScript cyclic/recursive interface definition

Base case基本情况

I'm having trouble defining a modular type system that suits my needs in a modular way.我无法定义以模块化方式满足我需求的模块化类型系统。 Here's a simple version of my problem :这是我的问题的一个简单版本:

I need two slightly different representations of pretty much the same types system : One for the frontend, one for the backend (I'll explain the difference later, now this is only for context).我需要几乎相同类型系统的两种略有不同的表示:一种用于前端,一种用于后端(稍后我将解释差异,现在这仅用于上下文)。 I'm trying to define a types system where I'm able to derive one from the other, as it seems theorically doable.我正在尝试定义一个类型系统,我可以在其中从另一个中派生出一个,因为这在理论上是可行的。

Here's how to understand the model :以下是理解模型的方法:

  • A User has multiple Team s.一个User有多个Team
  • A Team has multiple Unit s.一个Team有多个Unit

That's all there is conceptually : (A)这就是概念上的全部内容:(A)

type IUser = {
    username: string,
    teams: ITeam[],
};

type ITeam = {
    teamname: string,
    user: IUser ,
    units: IUnit[],
}

type IUnit = {
    unitname: string,
    team: ITeam ,
}

I need to be able to access each relation in both ways (both User -> Team , and Team -> User for example).我需要能够以两种方式访问​​每个关系(例如User -> TeamTeam -> User )。 From the User down to the Unit, and from the Unit up to the User.从用户到单位,从单位到用户。

Context语境

That works well to type what my frontend will play with.这很适合输入我的前端将使用的内容。 Now, here's what I need to represent in the backend : I'm using TypeORM, so each of these is an Entity and reality the code rather looks like this :现在,这是我需要在后端表示的内容:我使用的是 TypeORM,所以每一个都是一个实体,实际上代码看起来像这样:

(B) ( this is also mostly for context, there's a playground stripped of any TypeORM logic lower) (B)(这也主要是为了上下文,有一个没有任何 TypeORM 逻辑更低的操场)

import {Entity, Column, OneToMany, ManyToOne} from "typeorm";

@Entity()
class User extends BaseEntity {
    @Column()
    username: string,

    // A User has many Teams, loaded lazily
    @OneToMany(() => Team, (team) => team.user, {})
    teams: Promise<Team[]>,
};

@Entity()
class Team extends BaseEntity {
    @Column()
    teamname: string,

    // A Team is owned by one User, loaded eagerly
    @ManyToOne(() => User, (user) => user.teams, {
        eager: true,
    })
    user: User,

    // A Team has many Units, loaded lazily
    @OneToMany(() => Unit, (unit) => unit.team, {})
    units: Promise<Unit[]>,
}

@Entity()
class Unit extends BaseEntity {
    @Column()
    unitname: string,

    // A Unit is owned by one Team, loaded eagerly
    @ManyToOne(() => Team, (team) => team.units, {
        eager: true,
    })
    team: Team,
}

Now of course as the documentation of TypeORM tells us :现在当然正如TypeORM 的文档告诉我们的那样:

Eager relations can only be used on one side of the relationship, using eager: true on both sides of relationship is disallowed. Eager 关系只能在关系的一侧使用,不允许在关系的两侧使用 Eager: true。

So I chose a direction in which all relations would load eagerly, and an other where they'd load lazily.所以我选择了一个所有关系都会急切加载的方向,而另一个是它们会延迟加载的方向。 I decided I'd load the descending way lazily (cause in my use case I won't need every Team of a User every time), and load the ascending way eagerly (cause I'll want to know the User of a Unit every time in my use case).我决定懒惰地加载下降方式(因为在我的用例中我不会每次都需要一个用户的每个团队),并急切地加载上升方式(因为我想知道一个单位的用户每个在我的用例中的时间)。

What I want我想要的是

I want to verify that model (B) is just model (A) with some promisified fields.我想验证模型 (B) 只是具有一些承诺字段的模型 (A)。


Problem问题

Now I can't verify that model (B) has anything to do with model (A), because some of the properties the fields reference also have been promisified.现在我无法验证模型 (B) 与模型 (A) 有什么关系,因为字段引用的某些属性也已被承诺。


What I tried我试过的

You can follow along this TypeScript playground link, which strips this problems of the TypeORM context and focuses on the actual type definitions : Playground link你可以按照这个 TypeScript 游乐场链接,它去除了 TypeORM 上下文的这个问题,并专注于实际的类型定义: Playground 链接

I've put the two models I described earlier, as well as my current reasoning on how I could use one to derive from the other.我已经放置了我之前描述的两个模型,以及我目前关于如何使用一个模型从另一个模型中推导出来的推理。 But now I'm stuck : You can read until the end of the playground to understand that I'd need some kind of recursion that I don't know how to express.但现在我被困住了:你可以读到操场结束才能理解我需要某种我不知道如何表达的递归。

My approach for fixing this is to make sure that the frontend and backend versions both implement some shared interface.我解决这个问题的方法是确保前端和后端版本都实现了一些共享接口。 For this shared interface, I say that a value can either be a promise or a resolved value by creating a utility type MaybePromise<T> :对于这个共享接口,我通过创建一个实用程序类型MaybePromise<T>说一个值可以是一个 promise 或一个已解析的值:

type MaybePromise<T> = T | Promise<T>;

Using this utility type, I redefine the I interfaces to be the shared base, which might or might not have promises.使用这种实用程序类型,我将I接口重新定义为共享基础,它可能有也可能没有承诺。

type IUser = {
    username: string,
    teams: MaybePromise<ITeam[]>,
};

type ITeam = {
    teamname: string,
    user: MaybePromise<IUser>,
    units: MaybePromise<IUnit[]>,
}

type IUnit = {
    unitname: string,
    team: MaybePromise<ITeam>,
}

If we want to get back the original non-promised interfaces, we can do that using another utility type which will replace these maybe promises with their resolved values.如果我们想找回原始的非承诺接口,我们可以使用另一种实用程序类型来做到这一点,它将用它们的解析值替换这些可能的承诺。

type UnpromisifyFields<T> = {
    [key in keyof T]: T[key] extends MaybePromise<infer U> ? UnpromisifyFields<U> : T[key]
}

Making this recursive means that the resolved types aren't particularly readable, but they do seem to be correct.进行这种递归意味着解析的类型不是特别可读,但它们似乎是正确的。

type PureUser = UnpromisifyFields<IUser>
// becomes { username: string; teams: UnpromisifyFields<ITeam>[]; }

type PureTeam = UnpromisifyFields<ITeam>

type PureUnit = UnpromisifyFields<IUnit>

Our backend classes are able to implement the base MaybePromise interfaces:我们的后端类能够实现基本的MaybePromise接口:

class User implements IUser {    
    username!: string;
    teams!: Promise<Team[]>;
};

class Team implements ITeam {
    teamname!: string;
    user!: User;
    units!: Promise<Unit[]>;
}

class Unit implements IUnit {
    unitname!: string;
    team!: Team;
}

I think this solves most of your issues.我认为这可以解决您的大部分问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM