如何将这些类型谓词的类型分配给它们的参数类型?

How to assign these type predicates' types to their parameters' types?

以下是我想要实现的目标:

function isString<T>( value: T ): value is T extends string ? T : string {

  return typeof value === "string";

}
function isNotString<T>( value: T ): T extends string ? unknown : T {

  return typeof value !== "string";

}

但我收到一条错误消息:“类型谓词的类型必须可分配给其参数的类型。”这是预期的。

顺便说一句,我不想​​达到以下目标:

function isString<T>( value: T ): Extract<T, string> {

  return typeof value === "string";

}
function isNotString<T>( value: T ): Exclude<T, string> {

  return typeof value !== "string";

}

因为,这两个都可能推断出我不想要的 never 类型。也就是说,我总是需要有一个已知的类型来处理。

你能帮我实现这种 TypeScript 方式吗? :-)


回复答案

非常感谢你们富有洞察力的回答,伙计们。这是我的回复:

@Linda Paiste

Don't bother creating an isNotString type guard because it won't do what you want. Instead, check that isString is false and typescript will infer everything properly.

你看到下面的函数有什么问题吗?如果我没记错的话,我认为它达到了我的预期。

function isNotString<T>( value: T ): value is Exclude<T, string> {
    return typeof value === "string";
}

I personally like to include a generic and assert that value is T & string rather than just value is string so that no information is lost if the type is already known to be a specific subset of string.

我喜欢这种方法,但我之前以一种奇怪的方式实现了这一点。我不打算在这里分享这个,因为这是一种疯狂的方法。

We didn't have any never in our return statement, but we still get never in our code because it's the only logical type if our a: string variable is not string.

完全同意。昨天我肯定是疯了。 :-)

It's worth noting that typescript's built-in type guards don't actually refine the type in the negative case. Which just goes to show that you can't really do it properly when dealing with an unknown type like T.

我同意亲爱的。我只是想让它用于详细目的。也许我错了,但 isNotString 对我来说似乎比 ! isString.

更冗长

@Andrei Tătar

你能看看这个吗Playground

我可以看到 Exclude 正在按预期工作,但 value: T | string 不是。如果我遗漏了什么,请随时指出。

你可以这样做:

function isNotString<T>(value: T | string): value is T {
    return typeof value !== "string";
}

对于is字符串检查,您可以简单地:

function isString(value: any): value is string {
  return typeof value === "string";

}

简答

不要费心去创建一个 isNotString 类型保护,因为它不会做你想要的。相反,检查 isString 是否为假,打字稿将正确推断所有内容。

长答案

@Andrei 的回答是正确的,类型保护必须用 value is 断言它的 return。我想解决 never 的问题。类型保护是 true/false 检查。其中一个分支签出,另一个不合逻辑,因此它将是 never.

假设这是我们的类型保护。我个人喜欢包含一个泛型并断言 value is T & string 而不仅仅是 value is string,这样如果已知该类型是 string.[=42 的特定子集,则不会丢失任何信息。 =]

function isString<T>( value: T ) : value is T & string {
  return typeof value === "string";
}

现在我们想在 if 语句中使用这个类型保护。让我们看看当我们对一个类型已知的变量执行此操作时会发生什么。

const a: string = "something";

if (isString(a)) {
    console.log(a); // `a` here is `string`
} else {
    console.log(a); // `a` here is `never` 
}

我们的 return 语句中没有任何 never,但我们的代码中仍然有 never,因为如果我们的 a: string 它是唯一的逻辑类型变量不是 string.

定义一个 isNotString typeguard 并不是真正必要的,因为你也可以检查 ! isString()。但是如果我们想这样做,它会在用字符串调用时 return never 。其他任何事情都没有意义。只有当函数为真时才应用断言。所以如果 string 不是 string,那么它是什么?

老实说,很难定义断言否定案例的类型保护,即。 value is not string,因为类型保护旨在断言肯定。 Exclude 似乎 应该 工作,但它无法区分工会,因为工会不会扩展 string 所以它总是 returns never。我明白为什么这是您不想要的不良行为。所以不要这样做。你已经有一个守卫来查看某物是否是 string,所以用它来查看它是否是 而不是 string 它会做你想做的事。

function checkUnion(value: string | number) {
    if (!isString(value)) {
        console.log(value); // `value` is `number`
    }
    if (isString(value)) {
        console.log(value) // `value` is `string`
    }
}

值得注意的是,typescript 的内置类型保护实际上并没有在否定的情况下优化类型。这只是表明在处理像 T.

这样的未知类型时,你不能真正正确地做到这一点
function checkGeneric<T>(value: T) {
    if ( typeof value !== "string" ) {
        console.log(value); // value is still just T
    }
    if ( typeof value === "string" ) {
        console.log(value); // value is T & string
    }
}

Playground Link

所以,最后我想出了以下几点:

function isString<T>( value: T ): value is T & string {
    return typeof value === "string";
}
function isNotString<T>( value: T ): value is Exclude<T, string> {
    return typeof value === "string";
}

Playground.