假设我有一个界面

interface Foo {
   id: string;
   bar: string;
}

interface Person {
   name: string;
   age: number;
   birthdate: Date;
   active: boolean;
   foo: Foo;
}

我希望能够使用第一个接口的名称创建一个与另一个字符串连接的接口.在给定泛型类型FilterFor的情况下,我是否可以创建泛型类型

type FilterFor<T> = ...

使得调用FilterFor<Person>将产生以下接口:

interface FilterForPerson {
    name: string;
    name__in: string[];
    name__search: string;
    age: number;
    age__gt: number;
    age__gte: number;
    age__lt: number;
    age__lte: number;
    birthdate: Date;
    birthdate__gt: Date;
    birthdate__gte: Date;
    birthdate__lt: Date;
    birthdate__lte: Date;
    foo: string|Foo
    foo__in: Array<string|Foo>
    active: boolean
}

编辑:新增datefooactive字段

推荐答案

有多种可能的方法来实现这一点.我在这里提供的解决方案涉及key-remapping in mapped types,它允许您转换输入类型的键和值.我们将首先计算一个名为Fields<T>的中间类型,它将您的输入类型T转换为一个包含键值tuple types的大union,然后我们将对其结果进行FilterFor<T>运算,以获得最终类型.(感谢@Oblosys采用了这种方法).

所以Fields<{a: string, b: number}>大约是["a", string] | ["a__search", string] | ["a__in", string[]] | ["b", number] | ["b__gt", number] | ⋯,然后FilterFor<{a: string, b: number}>会从Fields<{a: string, b: number}>映射到{a: string, a__search: string, a__in: string[], b: number, b__gt: number, ⋯ }.


以下是我如何写出Fields<T>:

type Fields<T> = { [K in string & keyof T]:
  T[K] extends number | Date ? [K | `${K}__${"gt" | "gte" | "lt" | "lte"}`, T[K]] :
  T[K] extends object ? [K, string | T[K]] | [`${K}__in`, (string | T[K])[]] :
  T[K] extends string ? [K | `${K}__search`, string] | [`${K}__in`, string[]] :
  [K, T[K]]
}[string & keyof T]

这是表单{[K in KK]: F<K>}[KK]distributive object type,它最终为KK个并集中的每个K计算一个F<K>的并集.在上面的代码中,KKstring & keyof T,或者只是输入类型Tstring个键(这不包括number个键和symbol个键,如果它们存在的话).F<K>是一系列conditional types,它们构成了K的正确的键值元组.

我遵循的特殊规则是...首先在键K处判断属性T的值类型T[K].

  • 如果它是numberDate,那么我们需要一串键,包括K和带有各种比较后缀的Ktemplate-literal-type串联,值类型保持不变.

  • 否则,如果它是object(请注意,Dateobject,所以我们需要按照这个顺序),那么我们希望保持键K和值T[K]string的联合,并且我们还希望键K具有__in后缀,并且值arrayT[K]-与-string联合.

  • 否则,如果它是string,那么我们希望后缀为-__searchKK的值为string,而后缀为-__inK的值为string[].

  • 否则,只需保留键K和值T[K]不变.

一旦我们做到了这一点,FilterFor<T>就是:

type FilterFor<T> = { [F in Fields<T> as F[0]]: F[1] }

其仅使用第一个元素as的键和第二个元素作为值来映射Field<T>中的每个条目.


好的,让我们测试一下:

interface Foo {
  id: string;
  bar: string;
}

interface Person {
  name: string;
  age: number;
  birthdate: Date;
  active: boolean;
  foo: Foo;
}

type FilterForPerson = FilterFor<Person>
/* type FilterForPerson = {
    name: string;
    name__search: string;
    name__in: string[];
    age: number;
    age__gt: number;
    age__gte: number;
    age__lt: number;
    age__lte: number;
    birthdate: Date;
    birthdate__gt: Date;
    birthdate__gte: Date;
    birthdate__lt: Date;
    birthdate__lte: Date;
    active: boolean;
    foo: string | Foo;
    foo__in: (string | Foo)[];
} */

看上go 不错!agebirthdate属性得到numberDate的处理,foo属性得到非Dateobject的处理,name属性得到string处理,active得到一切处理.

Playground link to code

Typescript相关问答推荐

TypeScript类型不匹配:在返回类型中处理已经声明的Promise Wrapper

我应该使用什么类型的React表单onsubmit事件?

我可以以这样一种方式键入对象,只允许它的键作为它的值吗?

为什么不能使用$EVENT接收字符串数组?

在另一个类型的函数中,编译器不会推断模板文字类型

为什么Typescript不能推断类型?

在列表的指令中使用intersectionObservable隐藏按钮

尽管对象的类型已声明,但未解析的变量<;变量

如何编写在返回函数上分配了属性的类型安全泛型闭包

使用TypeScrip的类型继承

判断映射类型内的键值的条件类型

将对象的属性转换为正确类型和位置的函数参数

如何从对象或类动态生成类型

try 使Angular依赖注入工作

Typescript将键数组映射到属性数组

如何在Next.js和Tailwind.css中更改悬停时的字体 colored颜色

Typescript 从泛型推断类型

React router - 如何处理嵌套路由?

为什么 typescript 在对象合并期间无法判断无效键?

使用 Next.js 路由重定向到原始 URL 不起作用