简体   繁体   English

TypeScript 泛型条件类型作为泛型 function 的返回值

[英]TypeScript generic conditional type as return value for generic function

Background背景

As part of a data analysis library I'm working on, I'm creating a set of functions that read certain types of values out of strings.作为我正在研究的数据分析库的一部分,我正在创建一组函数,用于从字符串中读取某些类型的值。 The idea is essentially to define the structure of a CSV file, so I can read each of its cells as a string and, knowing what type it's supposed to be, convert that string into an appropriately typed value.这个想法本质上是定义 CSV 文件的结构,因此我可以将其每个单元格作为字符串读取,并且知道它应该是什么类型,然后将该字符串转换为适当类型的值。

Some cells contain multiple values, and get converted into arrays.一些单元格包含多个值,并被转换为 arrays。 Other cells contain single values.其他单元格包含单个值。

Part of what I'm doing here is removing values that don't match the expectations, and logging a warning.我在这里所做的部分工作是删除不符合预期的值,并记录警告。 For cells containing arrays, I'm quite happy with the result still being an array even if it's empty.对于包含 arrays 的单元格,我很高兴结果仍然是一个数组,即使它是空的。 But for cells containing single values, I want to convert invalid values to null .但是对于包含单个值的单元格,我想将无效值转换为null

To do this, I've set up a generic type to be shared by all these "transformer" functions, which returns a conditional type matching those requirements:为此,我设置了一个由所有这些“转换器”函数共享的泛型类型,它返回一个符合这些要求的条件类型:

type TransformerFn<T> = (value: string, locationIdentifier?: string) => T extends any[] ? T : (T | null);

In the simple cases I've implemented so far, such as for splitting a string into an array of strings or for extracting boolean value, this is working just fine.在我迄今为止实现的简单案例中,例如将字符串拆分为字符串数组或提取 boolean 值,这工作得很好。 TypeScript has had no problem resolving the condition for TransformerFn<string[]> or TransformerFn<boolean> . TypeScript 在解决TransformerFn<string[]>TransformerFn<boolean>的条件时没有问题。

But one of the transformers I have is essentially for confirming that every cell in a column is either empty or contains a value in a string enum , and here I've been running into a problem.但是我拥有的一个转换器本质上是用于确认列中的每个单元格要么是空的,要么在字符串enum中包含一个值,在这里我遇到了一个问题。

I've been typing that string enum as Record<string, string> when using it as a function argument, which has been working fine.当我将它用作 function 参数时,我一直在将该字符串枚举键入为Record<string, string> ,它一直运行良好。 However, recently I added some functionality to recode data that should clearly be a particular enum value, but hasn't been loaded correctly.但是,最近我添加了一些功能来重新编码显然应该是特定枚举值但尚未正确加载的数据。

To accomplish this, I've used a generic type with a constraint to represent the union of the string enum's values:为了实现这一点,我使用了一个带有约束的泛型类型来表示字符串枚举值的联合:

export function enumValue<E extends string>(enums: Record<string, E>, recodeMap?: Record<string, E>)

This has worked fine, up until I tried to add that TransformerFn typing I mentioned earlier.这一直很好,直到我尝试添加前面提到的TransformerFn类型。

The problem问题

Even though my generic type E has a constraint that it extends string , which means E extends any[] will never be true, TypeScript is failing to resolve my generic conditional type.即使我的泛型类型E具有extends string的约束,这意味着E extends any[]永远不会为真,但 TypeScript 无法解析我的泛型条件类型。

Here's the error it's giving me:这是它给我的错误:

Type '(value: string, locationIdentifier?: string | undefined) => E |类型'(值:字符串,locationIdentifier?:字符串|未定义)=> E | null' is not assignable to type 'TransformerFn'. null' 不能分配给类型 'TransformerFn'。 Type 'E | 'E 型 | null' is not assignable to type 'E extends any[]? null' 不能分配给类型 'E extends any[]? E: E |电子:电子| null'.无效的'。 Type 'null' is not assignable to type 'E extends any[]?类型“null”不能分配给类型“E extends any[]? E: E |电子:电子| null'.无效的'。

Here's my code:这是我的代码:

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;
}

TypeScript Playground TypeScript 游乐场

Minimum reproducible example最小可重现示例

type TransformerFn<T> = () => T extends any[] ? T : null;

function enumValue<E extends string>(): TransformerFn<E> {
    const transformer: TransformerFn<E> = () => {
        return null;
    };

    return transformer;
}

TypeScript Playground TypeScript 游乐场

Type '() => null' is not assignable to type 'TransformerFn'.类型 '() => null' 不可分配给类型 'TransformerFn'。 Type 'null' is not assignable to type 'E extends any[]?类型“null”不能分配给类型“E extends any[]? E: null'. E:空'。


Now of course I could just give up and use a non-conditional type for my enumValue function's return value, since I know it should be E | null现在我当然可以放弃并为我的enumValue函数的返回值使用非条件类型,因为我知道它应该是E | null E | null , instead of trying to use my TransformerFn conditional type. E | null ,而不是尝试使用我的TransformerFn条件类型。 My code will still work just fine, and it won't actually cause any maintenance issues for me.我的代码仍然可以正常工作,实际上不会对我造成任何维护问题。

But I've run into something I don't understand and haven't been able to figure out.但是我遇到了一些我不理解并且无法弄清楚的事情。 So, can anyone explain to me why this isn't working, and if there's something I could do instead that would work?那么,任何人都可以向我解释为什么这不起作用,如果有什么我可以做的事情会起作用吗?

This is a current limitation or missing feature of TypeScript;这是 TypeScript 的当前限制或缺失功能; the compiler generally defers evaluation of a conditional type which depends on an unresolved/unspecified generic type parameter, such as inside the implementation of a generic function.编译器通常会推迟对取决于未解析/未指定泛型类型参数的条件类型的评估,例如在泛型 function 的实现内部。 This is usually the best you can do, since the compiler can't generally know what the conditional type will be until it knows exactly what the checked type is.这通常是您可以做的最好的事情,因为编译器通常无法知道条件类型将是什么,直到它确切知道检查的类型是什么。 But: in some cases the generic type parameter is constrained in such a way that the compiler should be able to evaluate the conditional type earlier.但是:在某些情况下,泛型类型参数受到约束,编译器应该能够更早地评估条件类型。

For example, if you have a constrained generic type parameter T extends A , then anything like T extends A? X: Y例如,如果您有一个受约束的泛型类型参数T extends A ,那么像T extends A? X: Y T extends A? X: Y could conceivably be narrowed to X even when T is not known exactly. T extends A? X: Y可以想象,即使T不准确,Y 也可以缩小到X Or if there's another type B such that A & B is never , then T extends B? X: Y或者,如果有另一种类型B使得A & B never ,那么T extends B? X: Y T extends B? X: Y could conceivably be narrowed to Y even when T is not known exactly. T extends B? X: Y可以想象,即使T不准确,Y 也可以缩小为Y

Your example case is the latter: in the case that E extends string , then the conditional type E extends any[]? E: null;您的示例情况是后者:在E extends string的情况下,条件类型E extends any[]? E: null; E extends any[]? E: null; should evaluate to null , since the type string & any[] is essentially impossible (it's actually not so simple; types like string & any[] do not get reduced to never by the compiler and so it's possible for something other than null to come out there, but let's ignore that wrinkle here).应该评估为null ,因为类型string & any[]基本上是不可能的(它实际上并不那么简单;像string & any[]这样的类型不会被编译器简化为never ,因此可能出现null以外的其他东西在那里,但让我们忽略这里的皱纹)。

In any case, this sort of early evaluation of generic conditional types does not happen.无论如何,这种对泛型条件类型的早期评估不会发生。 There is a suggestion at microsoft/TypeScript#23132 to use generic constraints to evaluate conditional types. microsoft/TypeScript#23132建议使用泛型约束来评估条件类型。 That issue is still labeled as "awaiting feedback", so if you think your use case is particularly compelling (and not already mentioned in the issue) then you might want to go over there and describe it.该问题仍被标记为“等待反馈”,因此,如果您认为您的用例特别引人注目(并且该问题中尚未提及),那么您可能希望在那里 go 并对其进行描述。 In any case you could give it a.无论如何,你可以给它一个。 Realistically speaking, though, the issue has been open a long time and there's no indication that it will be implemented soon, or ever.但实际上,这个问题已经公开了很长时间,并且没有迹象表明它会很快或永远实施。


For now then you only have workarounds.现在,您只有解决方法。 In this case the best one is to use a type assertion , which are intended specifically for situations where the developer knows more about an expression's type than the compiler can figure out.在这种情况下,最好的方法是使用类型断言,它专门用于开发人员对表达式类型的了解多于编译器无法计算的情况。 If you are sure that transformer will be a valid TrandformerFn<E> , then you can just tell the compiler that:如果您确定transformer器将是有效的TrandformerFn<E> ,那么您可以告诉编译器:

const transformer = (() => {
    return null;
}) as TransformerFn<E>; // no error

Of course that means you're taking the job of maintaining type safety for that line away from the compiler, and so you should be careful that you're doing the job correctly, since the compiler cannot tell one way or the other:当然,这意味着您正在从编译器那里获得维护该行的类型安全的工作,因此您应该小心您正在正确地完成这项工作,因为编译器无法分辨出一种或另一种方式:

const badTransformer = (() => {
    return "oopsie";
}) as TransformerFn<E>; // also no error

Playground link to code Playground 代码链接

my generic type E has a constraint that it extends string , which means E extends any[] will never be true我的泛型E有一个约束,它扩展了string ,这意味着E extends any[]永远不会是真的

Not as far as Typescript is concerned.就 Typescript 而言,并非如此。 string & any[] is not never , but even if it was, never is also a type that E is allowed to be; string & any[]不是never ,但即使是, never也是允许E的类型; never is a subtype of every other type, so it does satisfy the constraint, and also never extends any[] is true. never是所有其他类型的子类型,因此它确实满足约束,并且never extends any[]为真。

Note that due to a quirk of how distributive conditional types work with never (see here ), your type TransformerFn<never> is actually () => never , meaning a function which never returns (ie it throws an exception or it doesn't terminate).请注意,由于分布式条件类型如何与never一起工作的怪癖(请参阅此处),您的类型TransformerFn<never>实际上是() => never ,这意味着 function 永远不会返回(即它抛出异常或它不会终止)。 Your function which returns null is not assignable to this type, so the error message is correct.您的 function 返回null不能分配给此类型,因此错误消息是正确的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM