我有一个函数,它接受一个重命名函数和一个对象,并根据重命名函数重命名对象的键。
export default function mapKeys<T extends { [s: string]: T } | ArrayLike<T>>(
renameFunction: (key: string) => string,
object: T,
): T {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [renameFunction(key), value]),
);
}
您可以像这样使用它:
mapKeys(key => key.toUpperCase(), { a: 1, b: 2 });
// => { A: 1, B: 2 }
问题是,TypeScript抱怨这两个,函数定义:
Type '{ [k: string]: T; }' is not assignable to type 'T'.
'{ [k: string]: T; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ [s: string]: T; } | ArrayLike<T>'.
以及应用程序的错误:
Type 'number' is not assignable to type '{ a: number; b: number; }'.
以及
Type 'number' is not assignable to type '{ a: number; b: number; }'.
有没有方法可以输入这个函数,从而编译这些错误?理想情况下,TypeScript还应该知道函数的输出形状,例如,如果renameFunction
类似于:
const rename = (a: string) => `${a}Error`;
其目的是:
{ email: 'foo' }
然后TypeScript知道,结果对象中的键是emailError
。
我试着把这个函数写得更一般一些,如下所示:
export default function mapKeys(
renameFunction: (key: string) => string,
object: object,
): object {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [renameFunction(key), value]),
);
}
这将编译,但TypeScript将丢失有关对象键的信息。
如果我有一个像{ name: 'foo' }
这样的对象和一个像rename: (a: string) => a + 'Foo'
这样的重命名函数,TypeScript不知道返回的对象的形状是{ nameFoo: string }
。
1条答案
按热度按时间vpfxa7rd1#
在这种情况下,你需要定义两个东西,对于每个
renameFunction
,实际的(运行时)实现和类型级实现。当然,你会受到类型系统的限制,所以你不能在类型系统中表示任何任意的运行时Map。因此,
renameFunction
将具有以下类型:其中
T
将是类型级函数,右侧是运行时实现。Renamer
,类型级函数实现的一部分,实现如下:其中
A
表示输入类型,type
表示输出类型。然后,这将是实际类型级函数的形状:
type
将是A
字段的结果,其中某个类型级函数被“调用”。这是类型级函数声明,你还需要另一面,你调用这个类型级函数的地方。
在这里,我们合并了表示类型级函数的
F
和表示参数的{A: A}
,并从表示类型级结果的"type"
字段中提取,这样,我们就在typescript中模拟了更高级的类型。然后,您可以在实际函数中使用该编码:
最后一部分是运行时实现,您需要在签名中指明类型级函数。
为了他的召唤:
result
的类型为:您还可以定义其他函数,如下所示:
那么,
result
类型将为