打字稿:将 类 的数组类型转换为构造的 类 数组类型
Typescript: converting type of array of classes to type of array of constructed classes
有一个问题我一直在努力解决。
我有一个函数,里面有两个参数:
- 类 - 类
数组
- mapper - 接受这些 类.
实例数组的函数
想象一下这样的函数:
const MapClasses = (classes, mapper) => {
const instances = classes.map((item) => new item())
return mapper(instances)
}
我如何为此创建打字稿声明,以确保 mapper 将始终接受 类[=47 的实例数组=].
用法示例:
class Coords {
x = 10
y = 10
}
class Names {
name = 'Some Name'
}
const mapper = ([coords, names]) => {
return {
x: coords.x,
y: coords.y,
myName: names.name,
}
}
const mapped = MapClasses([Coords, Names], mapper)
// { x: 10, y: 10, myName: 'Some Name'}
所以我认为应该可以检查映射器是否正在访问正确的值。
我是这样工作的:
type MapClasses = <Classes, Mapped>(
classes: {
[Property in keyof Classes]: new () => Classes[Property]
},
mapper: (instances: Classes) => Mapped,
) => Mapped
但是在这种情况下,错误仅显示在 类 参数上,而不是 mapper.
那么有什么方法可以扭转这种行为吗?
....
如有任何想法,我将不胜感激。
祝你有愉快的一天。
要清楚,你的问题是当你错误地调用MapClasses()
时,错误出现在classes
参数上而不是mapper
参数上:
MapClasses([Names, Coords], mapper) // error
// -------> ~~~~~ ~~~~~~ <---
// | |
// | Type 'typeof Coords' is not assignable to type 'new () => Names'.
// Type 'typeof Names' is not assignable to type 'new () => Coords',
这并没有错误,但您希望它以另一种方式发生,例如:
MapClasses([Names, Coords], mapper) // error
// -----------------------> ~~~~~~
// Type '[Names, Coords]' is not assignable to type '[Coords, Names]'.
为了实现这一点,我们需要更改 MapClasses
的签名,以便 generic type parameter is inferred from classes
and not from mapper
. This means we need classes
to have a higher priority inference site for the type parameter than mapper
does. The details of how the compiler chooses which values to infer types from are not really documented anywhere official; there is a section of the outdated TypeScript spec 在某个时候超越过去的状态。一个好的经验法则是编译器将以“最简单”的方式选择与类型参数相关的值。
所以我们需要重构调用签名,使 classes
的类型注释与类型参数的关系比 mapper
的类型注释更简单。这是一种方法:
const MapClasses = <C extends (new () => any)[], M>(
classes: [...C],
mapper: (instances: ElementInstanceType<C>) => M,
): M => {
const instances = classes.map((item) => new item()) as
ElementInstanceType<C>;
return mapper(instances)
}
type ElementInstanceType<C> =
{ [K in keyof C]: C[K] extends new () => infer R ? R : never };
类型参数 C
是我们关心的参数,我有 constrained it to be an array of construct signatures. Conceptually classes
is just of type C
, although I have written [...C]
using a variadic tuple type 给编译器一个提示,我们想将 classes
推断为一个元组和不是无序数组。
同时 mapper
是 (instance: ElementInstanceType<C>) => M
类型,其中 ElementInstanceType
是 mapped type whose properties are conditional types。它将构造签名类型的元组转换为其对应实例类型的元组。比较 [...C]
和 (instance: ElementInstanceType<C>) => M
,你会发现前者与 C
的关系比后者更简单。
这意味着当您调用 MapClasses
时,类型参数 C
将倾向于从 classes
中推断出来,而只是 checked mapper
.
让我们确保它有效。首先,我们需要看到非错误情况仍然会产生正确类型的值:
const mapped = MapClasses([Coords, Names], mapper) // okay
// const mapped: { x: number; y: number; myName: string; }
现在我们应该看看出现错误时会发生什么:
MapClasses([Names, Coords], mapper) // error
// -----------------------> ~~~~~~
// Type '[Names, Coords]' is not assignable to type '[Coords, Names]'.
是的,这就是我们想要看到的。
有一个问题我一直在努力解决。
我有一个函数,里面有两个参数:
- 类 - 类 数组
- mapper - 接受这些 类. 实例数组的函数
想象一下这样的函数:
const MapClasses = (classes, mapper) => {
const instances = classes.map((item) => new item())
return mapper(instances)
}
我如何为此创建打字稿声明,以确保 mapper 将始终接受 类[=47 的实例数组=].
用法示例:
class Coords {
x = 10
y = 10
}
class Names {
name = 'Some Name'
}
const mapper = ([coords, names]) => {
return {
x: coords.x,
y: coords.y,
myName: names.name,
}
}
const mapped = MapClasses([Coords, Names], mapper)
// { x: 10, y: 10, myName: 'Some Name'}
所以我认为应该可以检查映射器是否正在访问正确的值。
我是这样工作的:
type MapClasses = <Classes, Mapped>(
classes: {
[Property in keyof Classes]: new () => Classes[Property]
},
mapper: (instances: Classes) => Mapped,
) => Mapped
但是在这种情况下,错误仅显示在 类 参数上,而不是 mapper.
那么有什么方法可以扭转这种行为吗?
....
如有任何想法,我将不胜感激。
祝你有愉快的一天。
要清楚,你的问题是当你错误地调用MapClasses()
时,错误出现在classes
参数上而不是mapper
参数上:
MapClasses([Names, Coords], mapper) // error
// -------> ~~~~~ ~~~~~~ <---
// | |
// | Type 'typeof Coords' is not assignable to type 'new () => Names'.
// Type 'typeof Names' is not assignable to type 'new () => Coords',
这并没有错误,但您希望它以另一种方式发生,例如:
MapClasses([Names, Coords], mapper) // error
// -----------------------> ~~~~~~
// Type '[Names, Coords]' is not assignable to type '[Coords, Names]'.
为了实现这一点,我们需要更改 MapClasses
的签名,以便 generic type parameter is inferred from classes
and not from mapper
. This means we need classes
to have a higher priority inference site for the type parameter than mapper
does. The details of how the compiler chooses which values to infer types from are not really documented anywhere official; there is a section of the outdated TypeScript spec 在某个时候超越过去的状态。一个好的经验法则是编译器将以“最简单”的方式选择与类型参数相关的值。
所以我们需要重构调用签名,使 classes
的类型注释与类型参数的关系比 mapper
的类型注释更简单。这是一种方法:
const MapClasses = <C extends (new () => any)[], M>(
classes: [...C],
mapper: (instances: ElementInstanceType<C>) => M,
): M => {
const instances = classes.map((item) => new item()) as
ElementInstanceType<C>;
return mapper(instances)
}
type ElementInstanceType<C> =
{ [K in keyof C]: C[K] extends new () => infer R ? R : never };
类型参数 C
是我们关心的参数,我有 constrained it to be an array of construct signatures. Conceptually classes
is just of type C
, although I have written [...C]
using a variadic tuple type 给编译器一个提示,我们想将 classes
推断为一个元组和不是无序数组。
同时 mapper
是 (instance: ElementInstanceType<C>) => M
类型,其中 ElementInstanceType
是 mapped type whose properties are conditional types。它将构造签名类型的元组转换为其对应实例类型的元组。比较 [...C]
和 (instance: ElementInstanceType<C>) => M
,你会发现前者与 C
的关系比后者更简单。
这意味着当您调用 MapClasses
时,类型参数 C
将倾向于从 classes
中推断出来,而只是 checked mapper
.
让我们确保它有效。首先,我们需要看到非错误情况仍然会产生正确类型的值:
const mapped = MapClasses([Coords, Names], mapper) // okay
// const mapped: { x: number; y: number; myName: string; }
现在我们应该看看出现错误时会发生什么:
MapClasses([Names, Coords], mapper) // error
// -----------------------> ~~~~~~
// Type '[Names, Coords]' is not assignable to type '[Coords, Names]'.
是的,这就是我们想要看到的。