TypeScript 通用工厂函数类型,匹配数组元素顺序
TypeScript Generic Factory Function Type, matching array element order
我想构建某种 FactoryFactory
:基本上是 returns 工厂函数的通用函数。编写函数本身很简单,但我不知道如何为它进行 TypeScript 类型化。
函数应该这样使用:
const stubFactoryFunction = (...props) => (...values) => ({ /* ... */ });
const factory = stubFactoryFunction("prop1", "prop2");
const instance = factory("foo", 42);
console.log(instance); // { prop1: "foo", prop2: 42 }
起初我尝试以数组形式提供值类型:
type FactoryFunction<T extends any[]> =
<K extends string[]>(...props: K) =>
(...values: T[number]) =>
{[key in K[number]]: T[number]}
但这将导致 { prop1: string | number, prop2: string | number}
,因为类型与数组索引不匹配。
接下来我尝试将整个对象提供为通用类型:
type FactoryFunction<T extends {[key: string]: any}> =
(...props: (keyof T)[]) =>
(...values: ???) =>
T
在这里我遇到了类似的问题:values
必须以某种方式匹配 props
的顺序。
这可能吗?
奖励 1: 不允许重复道具。
奖励 2: 强制提供来自 T
.
的所有非可选道具
我不知道它是否以通用方式可行,因为映射类型可以映射值,而不是输入对象的键,这在这里是必需的。
参数数量有限的解决方案,这里从1到3:
type MonoRecord<P, V> = P extends string ? Record<P, V> : never;
type Prettify<T> = T extends infer Tbis ? { [K in keyof Tbis]: Tbis[K] } : never;
type FactoryFunctionResult<
P extends (string[] & { length: 1|2|3 }),
V extends (any[] & { length: P['length'] })
> =
P extends [infer P0] ?
MonoRecord<P0, V[0]> :
P extends [infer P0, infer P1] ?
Prettify<
MonoRecord<P0, V[0]> &
MonoRecord<P1, V[1]>> :
P extends [infer P0, infer P1, infer P2] ?
Prettify<
MonoRecord<P0, V[0]> &
MonoRecord<P1, V[1]> &
MonoRecord<P2, V[2]>> :
never;
type FactoryFunctionTest1 = FactoryFunctionResult<['a'], [string]>; // { a: string }
type FactoryFunctionTest2 = FactoryFunctionResult<['a', 'b'], [string, number]>; // { a: string; b: number }
type FactoryFunctionTest3 = FactoryFunctionResult<['a', 'b', 'c'], [string, number, boolean]>; // { a: string; b: number; c: boolean }
const stubFactoryFunction = <P extends (string[] & { length: 1|2|3 })>(...props: P) =>
<V extends (any[] & { length: P['length'] })>(...values: V) =>
({ /* ... */ } as FactoryFunctionResult<P, V>);
一些解释:
- 由于
& { length: 1|2|3 }
约束,参数数量限制为 3 个。
- 由于
& { length: P['length'] }
. ,内部工厂参数 (values
) 必须与外部工厂参数 (props
) 的数量相同
MonoRecord<P, V>
在 FactoryFunctionResult
中很有用,以确保每个 P0
..P2
扩展 string
否则我们会得到一个 TS 错误。除此之外,它只是一个带有 属性 的对象的别名。例如。 MonoRecord<'a', number>
给出 { a: number }
.
Prettify<T>
实用程序类型对于美化路口类型很有用。例如。 Prettify<{ a: number } & { b: string }>
给出 { a: number; b: string }
.
最多有 4 个参数:
- 更改约束条件
length: 1|2|3|4
- 在
FactoryFunctionResult
公式中添加第四种情况:
P extends [infer P0, infer P1, infer P2, infer P3] ?
Prettify<
MonoRecord<P0, V[0]> &
MonoRecord<P1, V[1]> &
MonoRecord<P2, V[2]> &
MonoRecord<P3, V[3]>> :
这是另一种解决方案,这次是通用的,并且最终没有那么复杂,尽管需要一些中间类型。基本想法是:
- 在字符串元组类型
P
(对于“道具”)(extends string[]
)中拥有一个带有键的对象类型是通过映射类型{ [K in P[number]]: ... }
完成的
- 难点在于获取
P
中K
(“Props”之一)的索引。它是使用另一个映射类型 IndexOf
完成的,它本身使用第三个映射类型 Indexes
.
- 然后,对象类型值由
V[IndexOf<P, K>]
给出,只有当 IndexOf<P, K>
是 V
的索引(对于“值”)时才可以接受,因此条件类型IndexOf<P, K> extends keyof V ? V[IndexOf<P, K>] : never
。我们永远不会有 never
,因为 P
和 V
数组类型由于 V extends (any[] & { length: P['length'] })
. 约束而具有相同的长度
// Utility types
type Indexes<V extends any[]> = {
[K in Exclude<keyof V, keyof Array<any>>]: K;
};
type IndexOf<V extends any[], T> = {
[I in keyof Indexes<V>]: V[I] extends T ? T extends V[I] ? I : never : never;
}[keyof Indexes<V>];
type FactoryFunctionResult<P extends string[], V extends (any[] & { length: P['length'] })> = {
[K in P[number]]: IndexOf<P, K> extends keyof V ? V[IndexOf<P, K>] : never;
};
// Tests
type IndexesTest1 = Indexes<['a', 'b']>; // { 0: "0"; 1: "1" }
type IndexOfTest1 = IndexOf<['a', 'b'], 'b'>; // "1"
type IndexOfTest2 = IndexOf<['a', 'b'], string>; // never
type IndexOfTest3 = IndexOf<[string, string], 'a'>; // never
type IndexOfTest4 = IndexOf<[string, string], string>; // "0" | "1"
type FactoryFunctionResultTest1 = FactoryFunctionResult<['a'], [string]>; // { a: string }
type FactoryFunctionResultTest2 = FactoryFunctionResult<['a', 'b'], [string, number]>; // { a: string; b: number }
type FactoryFunctionResultTest3 = FactoryFunctionResult<['a', 'b', 'c'], [string, number, boolean]>; // { a: string; b: number; c: boolean }
type FactoryFunctionResultTest4 = FactoryFunctionResult<['a', 'b', 'c', 'd'], [string, number, boolean, string]>; // { a: string; b: number; c: boolean; d: string }
我想构建某种 FactoryFactory
:基本上是 returns 工厂函数的通用函数。编写函数本身很简单,但我不知道如何为它进行 TypeScript 类型化。
函数应该这样使用:
const stubFactoryFunction = (...props) => (...values) => ({ /* ... */ });
const factory = stubFactoryFunction("prop1", "prop2");
const instance = factory("foo", 42);
console.log(instance); // { prop1: "foo", prop2: 42 }
起初我尝试以数组形式提供值类型:
type FactoryFunction<T extends any[]> =
<K extends string[]>(...props: K) =>
(...values: T[number]) =>
{[key in K[number]]: T[number]}
但这将导致 { prop1: string | number, prop2: string | number}
,因为类型与数组索引不匹配。
接下来我尝试将整个对象提供为通用类型:
type FactoryFunction<T extends {[key: string]: any}> =
(...props: (keyof T)[]) =>
(...values: ???) =>
T
在这里我遇到了类似的问题:values
必须以某种方式匹配 props
的顺序。
这可能吗?
奖励 1: 不允许重复道具。
奖励 2: 强制提供来自 T
.
我不知道它是否以通用方式可行,因为映射类型可以映射值,而不是输入对象的键,这在这里是必需的。
参数数量有限的解决方案,这里从1到3:
type MonoRecord<P, V> = P extends string ? Record<P, V> : never;
type Prettify<T> = T extends infer Tbis ? { [K in keyof Tbis]: Tbis[K] } : never;
type FactoryFunctionResult<
P extends (string[] & { length: 1|2|3 }),
V extends (any[] & { length: P['length'] })
> =
P extends [infer P0] ?
MonoRecord<P0, V[0]> :
P extends [infer P0, infer P1] ?
Prettify<
MonoRecord<P0, V[0]> &
MonoRecord<P1, V[1]>> :
P extends [infer P0, infer P1, infer P2] ?
Prettify<
MonoRecord<P0, V[0]> &
MonoRecord<P1, V[1]> &
MonoRecord<P2, V[2]>> :
never;
type FactoryFunctionTest1 = FactoryFunctionResult<['a'], [string]>; // { a: string }
type FactoryFunctionTest2 = FactoryFunctionResult<['a', 'b'], [string, number]>; // { a: string; b: number }
type FactoryFunctionTest3 = FactoryFunctionResult<['a', 'b', 'c'], [string, number, boolean]>; // { a: string; b: number; c: boolean }
const stubFactoryFunction = <P extends (string[] & { length: 1|2|3 })>(...props: P) =>
<V extends (any[] & { length: P['length'] })>(...values: V) =>
({ /* ... */ } as FactoryFunctionResult<P, V>);
一些解释:
- 由于
& { length: 1|2|3 }
约束,参数数量限制为 3 个。 - 由于
& { length: P['length'] }
. ,内部工厂参数 ( MonoRecord<P, V>
在FactoryFunctionResult
中很有用,以确保每个P0
..P2
扩展string
否则我们会得到一个 TS 错误。除此之外,它只是一个带有 属性 的对象的别名。例如。MonoRecord<'a', number>
给出{ a: number }
.Prettify<T>
实用程序类型对于美化路口类型很有用。例如。Prettify<{ a: number } & { b: string }>
给出{ a: number; b: string }
.
values
) 必须与外部工厂参数 (props
) 的数量相同
最多有 4 个参数:
- 更改约束条件
length: 1|2|3|4
- 在
FactoryFunctionResult
公式中添加第四种情况:
P extends [infer P0, infer P1, infer P2, infer P3] ?
Prettify<
MonoRecord<P0, V[0]> &
MonoRecord<P1, V[1]> &
MonoRecord<P2, V[2]> &
MonoRecord<P3, V[3]>> :
这是另一种解决方案,这次是通用的,并且最终没有那么复杂,尽管需要一些中间类型。基本想法是:
- 在字符串元组类型
P
(对于“道具”)(extends string[]
)中拥有一个带有键的对象类型是通过映射类型{ [K in P[number]]: ... }
完成的
- 难点在于获取
P
中K
(“Props”之一)的索引。它是使用另一个映射类型IndexOf
完成的,它本身使用第三个映射类型Indexes
. - 然后,对象类型值由
V[IndexOf<P, K>]
给出,只有当IndexOf<P, K>
是V
的索引(对于“值”)时才可以接受,因此条件类型IndexOf<P, K> extends keyof V ? V[IndexOf<P, K>] : never
。我们永远不会有never
,因为P
和V
数组类型由于V extends (any[] & { length: P['length'] })
. 约束而具有相同的长度
// Utility types
type Indexes<V extends any[]> = {
[K in Exclude<keyof V, keyof Array<any>>]: K;
};
type IndexOf<V extends any[], T> = {
[I in keyof Indexes<V>]: V[I] extends T ? T extends V[I] ? I : never : never;
}[keyof Indexes<V>];
type FactoryFunctionResult<P extends string[], V extends (any[] & { length: P['length'] })> = {
[K in P[number]]: IndexOf<P, K> extends keyof V ? V[IndexOf<P, K>] : never;
};
// Tests
type IndexesTest1 = Indexes<['a', 'b']>; // { 0: "0"; 1: "1" }
type IndexOfTest1 = IndexOf<['a', 'b'], 'b'>; // "1"
type IndexOfTest2 = IndexOf<['a', 'b'], string>; // never
type IndexOfTest3 = IndexOf<[string, string], 'a'>; // never
type IndexOfTest4 = IndexOf<[string, string], string>; // "0" | "1"
type FactoryFunctionResultTest1 = FactoryFunctionResult<['a'], [string]>; // { a: string }
type FactoryFunctionResultTest2 = FactoryFunctionResult<['a', 'b'], [string, number]>; // { a: string; b: number }
type FactoryFunctionResultTest3 = FactoryFunctionResult<['a', 'b', 'c'], [string, number, boolean]>; // { a: string; b: number; c: boolean }
type FactoryFunctionResultTest4 = FactoryFunctionResult<['a', 'b', 'c', 'd'], [string, number, boolean, string]>; // { a: string; b: number; c: boolean; d: string }