[英]Typescript: Typing Function Return Type based on Parameters
我想知道是否有办法根据给它的输入键入 function 的返回值。 这是我在想什么的一个例子:
function toEnum(...strings: string[]) {
const enumObject = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
const myEnum = toEnum('one', 'two', 'three')
有没有办法输入这个 function 以便我们知道myEnum
看起来像:
{
ONE: 'one',
TWO: 'two',
THREE: 'three'
}
编辑:
正如@dariosicily 提到的,我们可以使用Record<string, string>
或索引签名键入enumObject
,但我想知道是否有一种方法可以根据传入的参数知道返回 object 中存在的实际键。
有一个内在的Uppercase<T>
字符串操作实用程序类型,当给定一个string
文字类型作为输入时,它会生成一个大写版本 output。因此Uppercase<"abc">
和"ABC"
是相同的类型。 使用这种类型,我们可以创建一个具有重新映射键的映射类型来表达toEnum()
的 output 类型,给定其arguments的字符串文字类型的并集:
function toEnum<K extends string>(...strings: K[]): { [P in K as Uppercase<P>]: P } {
const enumObject: any = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
请注意, toEnum()
在K
中是通用的,它是strings
数组的元素类型的联合。 请注意, K
被限制为string
,因此strings
实际上是一个字符串数组,并且因为此限制给编译器一个提示,即我们要为其元素推断字符串文字类型,而不仅仅是string
。 你肯定需要在这里使用泛型,否则你只会从 function 中得到Record<string, string>
。
类型{[P in K as Uppercase<P>]: P}
遍历原始K
联合中的每个字符串P
并将其重新映射为大写版本作为键,然后使用与值相同的类型P
那就是你想要的类型。
另请注意,我将enumObject
为any
类型,以便在toEnum()
的实现中选择退出严格类型检查; 编译器无法遵循enumObject[str.toUpperCase()]=str
将是对{[P in K as Uppercase<P>]: P}
类型值的适当操作的逻辑,因此我们甚至不会让它尝试。
无论如何你可以测试它是否做了你想要的:
const myEnum = toEnum('one', 'two', 'three', "fortyFive");
/* const myEnum: {
ONE: "one";
TWO: "two";
THREE: "three";
FORTYFIVE: "fortyFive";
} */
console.log(myEnum.THREE) // "three" both at compile and runtime
在您提到的评论中,对于类似fortyFive
的内容,您希望密钥为FORTY_FIVE
而不是FORTYFIVE
。 也就是说,您不只是希望键是输入的大写版本。 您希望输入被解释为小驼峰大小写,而 output 被解释为全大蛇形大小写(也称为 SCREAMING_SNAKE_CASE)。
这在 TypeScript 中也是可能的,使用模板文字类型将字符串文字类型拆分为字符,并使用递归条件类型以编程方式对这些字符进行操作。
首先让我们在类型级别进行:
type LowerPascalToUpperSnake<T extends string, A extends string = ""> =
T extends `${infer F}${infer R}` ? LowerPascalToUpperSnake<R,
`${A}${F extends Lowercase<F> ? "" : "_"}${Uppercase<F>}`
> : A;
请注意,在值级别执行相同操作的 function 很有用:
function lowerPascalToUpperSnake<T extends string>(str: T) {
return str.split("").map(
c => (c === c.toLowerCase() ? "" : "_") + c.toUpperCase()
).join("") as LowerPascalToUpperSnake<T>
}
类型和 function 的行为相似; 这个想法是迭代字符串的每个字符,当且仅当当前字符不是小写时插入下划线,然后插入当前字符的大写版本。 您可以验证这是否有效:
const test = lowerPascalToUpperSnake("abcDefGhiJklmNop");
// const test: "ABC_DEF_GHI_JKLM_NOP"
console.log(test); // "ABC_DEF_GHI_JKLM_NOP"
运行时的值和编译器计算的类型一致。
现在我们可以在toEnum()
中使用“lower-Pascal-to-upper-snake”操作来代替原来的大写操作:
function toEnum<K extends string>(...strings: K[]):
{ [P in K as LowerPascalToUpperSnake<P>]: P } {
const enumObject: any = {};
strings.forEach((str) => {
enumObject[lowerPascalToUpperSnake(str)] = str;
});
return enumObject;
}
看看它的实际效果:
const myEnum = toEnum('one', 'two', 'three', "fortyFive");
/* const myEnum: {
ONE: "one";
TWO: "two";
THREE: "three";
FORTY_FIVE: "fortyFive";
} */
console.log(myEnum.FORTY_FIVE) // "fortyFive"
看起来挺好的!
问题是由于在 javascript 语言中,行enumObject[str.toUpperCase()] = str;
分配是合法的 typescript 同一行会导致错误,因为您没有明确声明enumObject
的索引签名或明确声明它为any
。
在这种情况下,解决问题的一种方法是使用内置Record
实用程序类型将其应用于您的enumObject
,如下所示:
function toEnum(...strings: string[]) {
const enumObject: Record<string, string> = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
const myEnum = toEnum('one', 'two', 'three')
//it will print { ONE: 'one', TWO: 'two', THREE: 'three' }
console.log(myEnum);
编辑:回答问题
有一种方法可以根据传入的参数知道返回 object 中存在的实际密钥
您可以使用Object.keys
方法并将键作为Array<string>
返回:
const myEnum = toEnum('one', 'two', 'three')
//it will print [ONE, TWO, THREE]
const keys = (Object.keys(myEnum) as Array<string>);
如果你想从键创建一个新类型,你可以使用typeof
:
const keys = (Object.keys(myEnum) as Array<string>);
//type KeysType = 'ONE' | 'TWO' | 'THREE'
type KeysType = typeof keys[number];
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.