如何有条件地获取数组中所有对象的键的并集?
How to conditionally get a union of keys of all objects in an array?
假设我有以下内容:
const common = {
"test": "Test",
"test2": "Test2"
}
const greetings = {
"hello": "Hello"
}
export const locales = (["en", "nl"] as const);
export type I18nMap = Record<typeof locales[number], {
common: typeof common;
greetings: typeof greetings;
}>;
我希望能够 select 像 "common"
这样的单个键或像 ["common", "greetings"]
这样的键数组。基于 selected 键,我希望能够 select selected 对象的任何键,我不确定如何实现,下面是我的尝试
type Namespace = keyof I18nMap[keyof I18nMap];
export function useTranslation<
T extends Namespace | Namespace[],
U extends T extends Namespace
? keyof I18nMap[keyof I18nMap][Namespace]
: keyof I18nMap[keyof I18nMap][Namespace][number]
>(namespace: T) {}
当 namspace 为“common”时,我希望只有 "test" | "test2"
可用于类型 U,当我选择数组 [“common”,“greetings”] 时,我希望联合 "test" | "test2" | "hello"
对于类型 U
首先,让我们将 {common: typeof common; greetings: typeof greetings}
类型移动到界面中,以便更容易使用:
interface I18nData {
common: typeof common;
greetings: typeof greetings;
}
export type I18nMap = Record<typeof locales[number], I18nData>;
type Namespace = keyof I18nData;
T
应该只是扩展 Namespace
(而不是 Namespace | Namespace[]
)并且 namespace
参数应该是 T | T[]
。当 namespace
是包含不同类型的数组时,TypeScript 将推断 T
为这些类型的并集。
那么,U
应该扩展 keyof I18nData[T]
。然而,这样做是行不通的:
export function useTranslation<
T extends Namespace,
U extends keyof I18nData[T]
>(namespace: T | T[], key: U) {}
// Argument of type 'string' is not assignable to parameter of type 'never'.
useTranslation(['common', 'greetings'], 'test');
这是因为 T
是 'common' | 'greetings'
而 I18nData[T]
变成了 {test1: string; test2: string} | {hello: string}
。因为联合类型之间没有公共键,所以 keyof I18ndata[T]
变成 never
.
要解决此问题,您可以使用分布式条件类型:
export function useTranslation<
T extends Namespace,
// For every T, get keyof I18nData[T]
U extends T extends unknown ? keyof I18nData[T] : never
>(namespace: T | T[], key: U) {}
// Works
useTranslation('common', 'test');
useTranslation('greetings', 'hello');
useTranslation(['common', 'greetings'], 'test');
useTranslation(['common', 'greetings'], 'hello');
// Fails
useTranslation('common', 'hello');
useTranslation('greetings', 'test');
useTranslation(['common'], 'hello');
假设我有以下内容:
const common = {
"test": "Test",
"test2": "Test2"
}
const greetings = {
"hello": "Hello"
}
export const locales = (["en", "nl"] as const);
export type I18nMap = Record<typeof locales[number], {
common: typeof common;
greetings: typeof greetings;
}>;
我希望能够 select 像 "common"
这样的单个键或像 ["common", "greetings"]
这样的键数组。基于 selected 键,我希望能够 select selected 对象的任何键,我不确定如何实现,下面是我的尝试
type Namespace = keyof I18nMap[keyof I18nMap];
export function useTranslation<
T extends Namespace | Namespace[],
U extends T extends Namespace
? keyof I18nMap[keyof I18nMap][Namespace]
: keyof I18nMap[keyof I18nMap][Namespace][number]
>(namespace: T) {}
当 namspace 为“common”时,我希望只有 "test" | "test2"
可用于类型 U,当我选择数组 [“common”,“greetings”] 时,我希望联合 "test" | "test2" | "hello"
对于类型 U
首先,让我们将 {common: typeof common; greetings: typeof greetings}
类型移动到界面中,以便更容易使用:
interface I18nData {
common: typeof common;
greetings: typeof greetings;
}
export type I18nMap = Record<typeof locales[number], I18nData>;
type Namespace = keyof I18nData;
T
应该只是扩展 Namespace
(而不是 Namespace | Namespace[]
)并且 namespace
参数应该是 T | T[]
。当 namespace
是包含不同类型的数组时,TypeScript 将推断 T
为这些类型的并集。
那么,U
应该扩展 keyof I18nData[T]
。然而,这样做是行不通的:
export function useTranslation<
T extends Namespace,
U extends keyof I18nData[T]
>(namespace: T | T[], key: U) {}
// Argument of type 'string' is not assignable to parameter of type 'never'.
useTranslation(['common', 'greetings'], 'test');
这是因为 T
是 'common' | 'greetings'
而 I18nData[T]
变成了 {test1: string; test2: string} | {hello: string}
。因为联合类型之间没有公共键,所以 keyof I18ndata[T]
变成 never
.
要解决此问题,您可以使用分布式条件类型:
export function useTranslation<
T extends Namespace,
// For every T, get keyof I18nData[T]
U extends T extends unknown ? keyof I18nData[T] : never
>(namespace: T | T[], key: U) {}
// Works
useTranslation('common', 'test');
useTranslation('greetings', 'hello');
useTranslation(['common', 'greetings'], 'test');
useTranslation(['common', 'greetings'], 'hello');
// Fails
useTranslation('common', 'hello');
useTranslation('greetings', 'test');
useTranslation(['common'], 'hello');