Mar*_*nna 7 typescript typescript-generics conditional-types
作为我正在开发的数据分析库的一部分,我正在创建一组从字符串中读取某些类型的值的函数。这个想法本质上是定义 CSV 文件的结构,这样我就可以将其每个单元格作为字符串读取,并且知道它应该是什么类型,然后将该字符串转换为适当类型的值。
有些单元格包含多个值,并被转换为数组。其他单元格包含单个值。
我在这里所做的部分工作是删除与预期不符的值,并记录警告。对于包含数组的单元格,我很高兴结果仍然是一个数组,即使它是空的。但对于包含单个值的单元格,我想将无效值转换为null.
为此,我设置了一个由所有这些“转换器”函数共享的泛型类型,它返回与这些要求匹配的条件类型:
type TransformerFn<T> = (value: string, locationIdentifier?: string) => T extends any[] ? T : (T | null);
Run Code Online (Sandbox Code Playgroud)
在我迄今为止实现的简单情况下,例如将字符串拆分为字符串数组或提取布尔值,这工作得很好。TransformerFn<string[]>TypeScript 在解决or的条件时没有问题TransformerFn<boolean>。
但我拥有的一个变压器本质上是为了确认列中的每个单元格要么为空,要么包含 string 中的值enum,在这里我遇到了问题。
我一直在输入该字符串枚举,就像Record<string, string>将其用作函数参数时一样,它运行良好。但是,最近我添加了一些功能来重新编码显然应该是特定枚举值但尚未正确加载的数据。
为了实现这一点,我使用了带有约束的泛型类型来表示字符串枚举值的并集:
export function enumValue<E extends string>(enums: Record<string, E>, recodeMap?: Record<string, E>)
Run Code Online (Sandbox Code Playgroud)
这一直工作得很好,直到我尝试添加TransformerFn之前提到的打字。
尽管我的泛型类型E有一个约束 it extends string,这意味着E extends any[]永远不会为真,但 TypeScript 无法解析我的泛型条件类型。
这是它给我的错误:
类型 '(值: 字符串, locationIdentifier?: 字符串 | 未定义) => E | null' 不可分配给类型'TransformerFn'。类型 'E | null' 不能分配给类型 'E extends any[] ?乙:乙| 无效的'。类型“null”不可分配给类型“E extends any[]”?乙:乙| 无效的'。
这是我的代码:
type TransformerFn<T> = (value: string, locationIdentifier?: string) => T extends any[] ? T : (T | null);
/**
* Checks that the value, if it exists, is a member of an enum.
*
* If the value does not exist, it is transformed to null.
*
* If a recoding map is passed, and it contains instructions for this value, it is recoded first.
*
* If the value exists but it is not a member of the enum and cannot be recoded,
* a warning will be generated and null will be returned.
*/
export function enumValue<E extends string>(enums: Record<string, E>, recodeMap?: Record<string, E>): TransformerFn<E> {
const enumValues: E[] = Object.values(enums);
function isEnumMember(val: unknown): val is E {
return (enumValues as any[]).includes(val);
}
const transformer: TransformerFn<E> = (value: string, locationIdentifier?: string) => {
if (!value) {
return null;
}
if (isEnumMember(value)) {
return value;
}
if (recodeMap && value in recodeMap) {
const recodedValue = recodeMap[value];
return recodedValue;
}
console.warn(`Value '${value}' does not exist within ${enumValues.join(', ')} (${locationIdentifier})`);
return null;
};
return transformer;
}
Run Code Online (Sandbox Code Playgroud)
type TransformerFn<T> = () => T extends any[] ? T : null;
function enumValue<E extends string>(): TransformerFn<E> {
const transformer: TransformerFn<E> = () => {
return null;
};
return transformer;
}
Run Code Online (Sandbox Code Playgroud)
类型“() => null”不可分配给类型“TransformerFn”。类型“null”不可分配给类型“E extends any[]”?E:空'。
当然,现在我可以放弃并使用非条件类型作为enumValue函数的返回值,因为我知道它应该是E | null,而不是尝试使用我的TransformerFn条件类型。我的代码仍然可以正常工作,并且实际上不会给我带来任何维护问题。
但我遇到了一些我不明白并且无法弄清楚的事情。那么,谁能向我解释为什么这不起作用,以及我是否可以做一些事情来代替它?
jca*_*alz 10
这是 TypeScript 当前的限制或缺失的功能;编译器通常会推迟对依赖于未解析/未指定的泛型类型参数的条件类型的求值,例如在泛型函数的实现内部。这通常是您能做的最好的事情,因为编译器通常无法知道条件类型是什么,直到它确切地知道检查的类型是什么。但是:在某些情况下,泛型类型参数受到限制,编译器应该能够更早地评估条件类型。
例如,如果您有一个受约束的泛型类型参数T extends A,那么类似的任何内容都T extends A ? X : Y可以想象地缩小到X即使在T未知的情况下也是如此。或者,如果存在另一种类型,B例如A & B,never那么T extends B ? X : Y可以想象,Y即使T不确切知道,也可以缩小范围。
您的示例情况是后者:在这种情况下E extends string,那么条件类型E extends any[] ? E : null;应该评估为null,因为该类型string & any[]本质上是不可能的(实际上并不那么简单;像这样的类型string & any[]不会never被编译器简化为,因此有可能是其他类型比null出来,但让我们忽略这里的皱纹)。
无论如何,这种对泛型条件类型的早期评估不会发生。microsoft/TypeScript#23132建议使用泛型约束来评估条件类型。该问题仍然被标记为“等待反馈”,因此,如果您认为您的用例特别引人注目(并且问题中尚未提及),那么您可能需要去那里描述它。无论如何,你都可以给它一个 . 但实际上,这个问题已经存在很长时间了,没有迹象表明它会很快或永远实施。
现在你只有解决方法。在这种情况下,最好的方法是使用类型断言,它专门用于开发人员对表达式类型的了解比编译器了解的更多的情况。如果您确定这transformer将是有效的TrandformerFn<E>,那么您可以告诉编译器:
const transformer = (() => {
return null;
}) as TransformerFn<E>; // no error
Run Code Online (Sandbox Code Playgroud)
当然,这意味着您要承担维护该行的类型安全的工作,远离编译器,因此您应该小心地确保正确地完成这项工作,因为编译器无法区分其中一种方式:
const badTransformer = (() => {
return "oopsie";
}) as TransformerFn<E>; // also no error
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6578 次 |
| 最近记录: |