TypeScript 函数中的复杂签名
下面的通用函数接受一个函数对象,并返回一个对象,该对象具有相同的属性名,其值为对应函数的返回值。
const combineValues = obj => {
const res = {};
for (const k in obj){
res[k] = obj[k]()
}
return res;
}
const combined = combineValues({
n: () => 1,
s: () => 's'
}); // { n: 1, s: 's' }
尝试在 TypeScript 中为这个函数定义签名和实现
const combineValues =
<K extends string> (obj: Record<K, () => any>): Record < K, any > => {
const res = {} as Record<K, any>;
for (const k in obj){
res[k] = obj[k]()
}
return res;
}
const combined = combineValues({
n, s
}) // Record<"n" | "s", any>
但是 combined 不保留值的原始类型
const combcombineValues = <K extends string, T>(obj: Record<K, () => T>): Record<K, T> => {
const res = {} as Record<K, T>;
for (const k in obj){
res[k] = obj[k]()
}
return res;
}
仅当所有函数属性返回相同类型 T 时才有效
有可能在 Typescript 中完全定义它吗?
答案#1
在当前版本的 TypeScript(v2.7)中,可以使用映射类型的推断来完成此操作,而无需等待条件类型,这是 TypeScript 的一个特性,函数可以从输出向输入推断类型,似乎是“反向”的。
首先,让我们定义映射类型 Funcs<T>,它接受一个普通对象并将其转换为一个对象,该对象的属性都是返回普通对象属性类型的函数:
type Funcs<T> = { [K in keyof T]: (...args:any[]) => T[K] }
注意这种类型基本上与您想做的相反。现在让我们输入 combineValues():
const combineValues = <T>(obj: Funcs<T>): T => {
const res = {} as T; // only change in body
for (const k in obj) {
res[k] = obj[k]()
}
return res;
}
如您所见,它以 Funcs<T> 作为输入,并返回一个 T 作为输出。让我们看看它是否有效:
const combined = combineValues({
n: () => 1,
s: () => 's'
}); // { n: number, s: string }
这几乎是你想要的。唯一的区别是 TypeScript 往往将 () => 1 解释为返回 number 的函数,而不是返回字面量 1 的函数。你可以做一些事情来解决这个问题;最简单的方法(虽然有点重复)是断言返回类型字面量:
const combined = combineValues({
n: () => 1 as 1,
s: () => 's' as 's'
}); // { n: 1, s: 's' }
希望这有帮助。祝你好运!
更新:有人注意到,在 TypeScript 2.7 中,编译器似乎将 combined 的类型标注为 {n: any, s: any}。幸运的是,这似乎只会在你用 IntelliSense 检查类型时发生,正如(我认为)在 Microsoft/TypeScript#14041 中提到的。如果你实际使用该值,你会看到它具有正确的类型:
combined.n = 0 // error, 0 is not assignable to 1
combined.s = 0 // error, 0 is not assignable to 's'
操场链接
所以,我想我还是坚持这个答案,尽管 Intellisense 的错误很不幸。再次祝你好运!
答案#2
你可以在 TypeScript 2.8 中使用条件类型实现这一点(在撰写本文时 2.8 版本尚未发布,你可以通过 npm install -g typescript@next 获取,它计划于三月发布)。
// ReturnType<T> below is from 2.8 and is a conditional type
type AllReturnTypes<T extends { [name: string]: (...args: any[]) => any }> = { [P in keyof T]: ReturnType<T[P]> }
const combineValues = <T extends { [name: string]: () => any }>(obj: T): AllReturnTypes<T> => {
const res = {} as AllReturnTypes<T>;
for (const k in obj) {
res[k] = obj[k]()
}
return res;
}
const combined = combineValues({
n: () => 1, s: () => ""
}) // will be of type { n: number, s: string }
