如何根据 union/intersection 对象类型的值创建类型?
How to make a type from the values of an union/intersection object type?
我有一个困难的类型,想知道如何将类型定义为预定义对象类型中所有可能值的联合。
假设我们有一个自动生成的类型 Person
如下所示:
type Person = {
favouriteColor: string
age: number
female: boolean
}
如何使用 Person
类型创建等于 string | number | boolean
的联合类型?
在我的用例中,类型 Person
是自动生成的。我在对象上使用 Ramda 的 map 函数,将函数应用于对象的每个值:
import { map } from 'ramda'
classroom.people.forEach(person =>
// Ramda’s `map` is applied to a `block` object here:
map<Person, Person>(property => {
// The type definitions for Ramda are not sufficiently strong to infer the type
// of `property`, so it needs to be manually annotated.
return someFunction(property)
}, person)
)
我正在寻找的行为本质上等同于 keyof
— 但据我所知,TypeScript 中没有 valueof
。等效的实现会是什么样子?
非常感谢!
编辑: 通常,解决方案将按照@kaya3 的建议:type ValueOf<T> = T[keyof T]
。但是,仔细检查后,我的情况似乎受到以下问题的困扰:
type PersonCommonFields = {
age: number,
name: string
}
type PersonFragment =
| { favouriteColor: string }
| { female: boolean }
| { eyeColor: Color }
| { born: Date }
type Person = PersonCommonFields & PersonFragment
在这种情况下,ValueOf<Person>
如上定义 returns number | string
,即仅来自 PersonCommonFields
的值,忽略 PersonFragment
。此示例的预期结果为 number | string | boolean | Color | Date
.
是否有其他方法可以解决这种情况?
很多(很多!)提前致谢!
我注意到,如果您将 PersonFragment
中的 |
更改为 &
,它会起作用(换句话说,创建单一类型而不是联合类型)。您似乎希望这些字段是可选的,您可以将 Partial
与单一类型一起使用吗(与将每个字段设为可选的行为相同)?
type PersonCommonFields = {
age: number,
name: string
}
type PersonFragment = Partial<{
favouriteColor: string,
female: boolean,
eyeColor: Color,
born: Date
}>
type Person = PersonCommonFields & PersonFragment;
type PersonTypes = Person[keyof Person]; // number | string | boolean | Color | Date
编辑:
@kaya3 在评论中指出,当前行为是至少应设置 PersonFragment
中的一个字段。如果这不是必需的,那么上面的方法应该有效。
假设要求实际上只存在 1 个字段,而不是更多?您可以使用自定义类型 XOR
1 来强制执行此操作,生成的对象将允许您访问密钥。
// Note: Define these in your global typings file so they can be reused
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
type PersonFragment = XOR<
{ favouriteColor: string },
XOR<{ female: boolean }, XOR<{ eyeColor: Color }, { born: Date }>>
>;
1:
受@Wex 回答的启发,我找到了一个解决方法,虽然不完美但现在已经足够了。
graphql-codegen
生成以下类型:
type PersonCommonFields = {
age: number,
name: string
}
type A = { favoriteColor: string }
type B = { female: boolean }
type C = { eyeColor: Color }
type D = { born: Date }
type PersonFragment = A | B | C | D
由此我创建了 type Person = PersonCommonFields & PersonFragment
,它在代码中使用。理想情况下,只有最后一种类型 (Person
) 会被用来提出解决方案,因为随着它的发展和包含更多类型,它不需要与 PersonFragment
保持同步随着时间的推移,随着 GraphQL 模式的发展,它的联合(E
、F
、G
等)。
但是稍微重新排列一下,我们可以得到这个:
type PersonFields =
| Person[keyof PersonCommonFields]
| A[keyof A]
| B[keyof B]
| C[keyof C]
| D[keyof D]
创建所需的类型 number | string | favoriteColor | boolean | Color | Date
。
ValueOf<T> = T[keyof T]
类型对联合类型不起作用,因为 keyof
不分布在联合上。例如,类型 keyof ({foo: 1} | {bar: 2})
是 never
而不是 'foo' | 'bar'
.
相反,我们可以使用 distributive conditional type,它 分配给联合(顾名思义)。
type ValueOfUnion<T> = T extends infer U ? U[keyof U] : never
type Test = ValueOfUnion<Person>
// Test = string | number | boolean | Color | Date
我有一个困难的类型,想知道如何将类型定义为预定义对象类型中所有可能值的联合。
假设我们有一个自动生成的类型 Person
如下所示:
type Person = {
favouriteColor: string
age: number
female: boolean
}
如何使用 Person
类型创建等于 string | number | boolean
的联合类型?
在我的用例中,类型 Person
是自动生成的。我在对象上使用 Ramda 的 map 函数,将函数应用于对象的每个值:
import { map } from 'ramda'
classroom.people.forEach(person =>
// Ramda’s `map` is applied to a `block` object here:
map<Person, Person>(property => {
// The type definitions for Ramda are not sufficiently strong to infer the type
// of `property`, so it needs to be manually annotated.
return someFunction(property)
}, person)
)
我正在寻找的行为本质上等同于 keyof
— 但据我所知,TypeScript 中没有 valueof
。等效的实现会是什么样子?
非常感谢!
编辑: 通常,解决方案将按照@kaya3 的建议:type ValueOf<T> = T[keyof T]
。但是,仔细检查后,我的情况似乎受到以下问题的困扰:
type PersonCommonFields = {
age: number,
name: string
}
type PersonFragment =
| { favouriteColor: string }
| { female: boolean }
| { eyeColor: Color }
| { born: Date }
type Person = PersonCommonFields & PersonFragment
在这种情况下,ValueOf<Person>
如上定义 returns number | string
,即仅来自 PersonCommonFields
的值,忽略 PersonFragment
。此示例的预期结果为 number | string | boolean | Color | Date
.
是否有其他方法可以解决这种情况?
很多(很多!)提前致谢!
我注意到,如果您将 PersonFragment
中的 |
更改为 &
,它会起作用(换句话说,创建单一类型而不是联合类型)。您似乎希望这些字段是可选的,您可以将 Partial
与单一类型一起使用吗(与将每个字段设为可选的行为相同)?
type PersonCommonFields = {
age: number,
name: string
}
type PersonFragment = Partial<{
favouriteColor: string,
female: boolean,
eyeColor: Color,
born: Date
}>
type Person = PersonCommonFields & PersonFragment;
type PersonTypes = Person[keyof Person]; // number | string | boolean | Color | Date
编辑:
@kaya3 在评论中指出,当前行为是至少应设置 PersonFragment
中的一个字段。如果这不是必需的,那么上面的方法应该有效。
假设要求实际上只存在 1 个字段,而不是更多?您可以使用自定义类型 XOR
1 来强制执行此操作,生成的对象将允许您访问密钥。
// Note: Define these in your global typings file so they can be reused
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
type PersonFragment = XOR<
{ favouriteColor: string },
XOR<{ female: boolean }, XOR<{ eyeColor: Color }, { born: Date }>>
>;
1:
受@Wex 回答的启发,我找到了一个解决方法,虽然不完美但现在已经足够了。
graphql-codegen
生成以下类型:
type PersonCommonFields = {
age: number,
name: string
}
type A = { favoriteColor: string }
type B = { female: boolean }
type C = { eyeColor: Color }
type D = { born: Date }
type PersonFragment = A | B | C | D
由此我创建了 type Person = PersonCommonFields & PersonFragment
,它在代码中使用。理想情况下,只有最后一种类型 (Person
) 会被用来提出解决方案,因为随着它的发展和包含更多类型,它不需要与 PersonFragment
保持同步随着时间的推移,随着 GraphQL 模式的发展,它的联合(E
、F
、G
等)。
但是稍微重新排列一下,我们可以得到这个:
type PersonFields =
| Person[keyof PersonCommonFields]
| A[keyof A]
| B[keyof B]
| C[keyof C]
| D[keyof D]
创建所需的类型 number | string | favoriteColor | boolean | Color | Date
。
ValueOf<T> = T[keyof T]
类型对联合类型不起作用,因为 keyof
不分布在联合上。例如,类型 keyof ({foo: 1} | {bar: 2})
是 never
而不是 'foo' | 'bar'
.
相反,我们可以使用 distributive conditional type,它 分配给联合(顾名思义)。
type ValueOfUnion<T> = T extends infer U ? U[keyof U] : never
type Test = ValueOfUnion<Person>
// Test = string | number | boolean | Color | Date