我经常偶然发现neverTypeScript 中的类型。虽然我已经阅读了文档,浏览了有关 SO 的现有问题 - 我的理解仍然不是很好。我希望得到一些帮助,以便更好地理解它。下面是 TS 文档中的一个让我头晕的例子:
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
? Return
: never;
type Num = GetReturnType<() => number>;
type Str = GetReturnType<(x: string) => string>;
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
Run Code Online (Sandbox Code Playgroud)
我的理解是,指定(...args: never[]) => ...意味着创建一个不接受任何参数的函数签名(为什么never[]?)。然而Str和Bools都是带参数的函数签名(这个错误不应该吗?)。
我也在努力理解条件类型:infer Return ? Return : never。对我来说,这意味着该函数要么有返回值,要么编译器应该产生错误。但我不确定。
kay*_*ya3 18
never是底型的。这意味着这never是一个没有值的类型,并且它是所有其他类型的子类型。如果您想象一个类型对应于一组可分配给该类型的值,则never对应于空集。
我的理解是,指定
(...args: never[]) => ...意味着创建一个不接受任何参数的函数签名(为什么never[]?)。
从技术上讲,你几乎是正确的;type 的数组never[]不能包含任何值,但空数组可以分配给 type never[],因为它的所有零元素都是 type never。(...args: never[]) => ...因此从技术上讲,可以使用零参数调用类型的函数。
也就是说,这并不是这里真正发生的事情。为了理解T extends (...args: never[]) => infer R,我们需要讨论协变和逆变。为了便于解释,我们假设我们有两种类型,Animaland Dog, andDog是 的子类型Animal:
首先考虑函数类型() => Animal和() => Dog,我们分别将其称为GetAnimal和GetDog。AGetAnimal是一个函数,可以调用它来获取Animal; GetDog可以调用a来获取 an Animal,因为 aDog是一个Animal,因此(根据里氏替换原理)GetDog是 的子类型GetAnimal。这意味着函数类型与其返回类型是协变的,因为如果用子类型替换函数的返回类型,那么您将获得原始函数类型的子类型。(反之亦然,如果用超类型替换返回类型,那么您将获得原始函数类型的超类型。)
现在考虑类型(a: Animal) => void和(a: Dog) => void,我们分别将其称为TakeAnimal和TakeDog。ATakeDog是一个不一定接受 any 的函数,因此您不能在需要a 的任何地方Animal使用 a ;这意味着不是 的子类型。另一方面, a是一个可以接受任何类型动物的函数,包括 a ,因此它是一个接受 a 的函数,因此(再次通过 LSP)是 的子类型。这意味着函数类型是TakeDogTakeAnimalTakeDogTakeAnimalTakeAnimalDogDogTakeAnimalTakeDog与其参数类型是逆变的,因为如果将函数的参数类型替换为超类型,那么您将获得原始函数类型的子类型。(反之亦然,如果将参数类型替换为子类型,那么您将获得原始函数类型的超类型。)
这归结为函数类型(...args: never[]) => R是返回的所有其他函数类型的超类型R,因为never[]是每个数组类型的子类型(因为never是每个类型的子类型,并且数组类型与其组件类型协变),并且它出现在逆变位置。所以T extends (...args: never[]) => ...真正意味着T可以有任何参数类型。
我个人认为这是一种不好的写作方式GetReturnType;写起来比较正常T extends (...args: any) => ...,或者T extends (...args: any[]) => ...说因为这样更容易理解,而且也有效。请注意,Typescript 有一个内置的帮助器类型ReturnType,它的作用与 , 相同GetReturnType,定义如下:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Run Code Online (Sandbox Code Playgroud)
在这个定义中,类型参数T具有(...args: any) => any其上限,因此您不能使用它来尝试获取非函数的返回类型。另外,这意味着extends保证满足条件类型的子句,因此never保证不会使用“假”分支(在您找到的定义中),因此无论存在什么类型(此处是的any,但我认为这样写会更清楚never)。
我也在努力理解条件类型:
infer Return ? Return : never。对我来说,这意味着该函数要么有返回值,要么编译器应该产生错误。但我不确定。
正如另一个答案中提到的,你已经解析了这个错误;整个extends子句就是条件,所以这应该读作(T extends (...args: never[]) => infer R) ? R : never。
更相关的是,这并不意味着当函数没有返回类型时编译器应该产生错误;这意味着结果应该是 type never,这不会导致错误,并且可以说是正确的结果。可以这样想:如果一个值是可分配的,那么当您调用它ReturnTypeOf<T>时,某种类型应该可以T返回该值。如果T不是一个函数,那么它不能返回任何值,因为你不能调用它,所以ReturnType<T>应该是一个空集。
通常never意思是“这个类型,在这个范围内,不能使用”。当实例化您不需要知道其类型的泛型参数时,这非常有用。在这种情况下,要获取函数的返回值,其参数并不重要。所以它们可以安全地输入为never.
所以never[]这里的意思是“某种类型的数组,请认为它是一个类型错误,假设它是任何特定类型”。
这比any[]在这种情况下更可取,因为any可以让您对该类型执行任何操作,并且通常它的使用意味着类型安全性的损失。
它在泛型类型别名之外没有那么有用。如果您尝试更直接地使用它:
type Fn = (...args: never[]) => string
const fn: Fn = (a: number, b: string, c: boolean) => a.toFixed()
fn(123) // Argument of type 'number' is not assignable to parameter of type 'never'.(2345)
Run Code Online (Sandbox Code Playgroud)
它允许出于相同的原因进行分配GetReturnType,但是当您尝试使用或引用 a 时never,您将收到类型错误。
问题的第二部分是完全不同的事情。首先,你的逻辑分组不正确。它更像是:
(Type extends (...args: never[]) => infer Return) ? Return : never
Run Code Online (Sandbox Code Playgroud)
或者用伪代码:
if (Type is subtype of ((...args: never[]) => infer Return) {
use type Return
} else {
use type never
}
Run Code Online (Sandbox Code Playgroud)
infer允许您从较大的类型中提取深度嵌套的类型,但它仅适用于A extends B样式三元内部。当您看到 时infer,请将类型视为带有通配符的模式。无论通配符是什么,都将被推断为您提供的任何名称的类型,然后它将在三元组的正侧可用。
一个更简单的例子:
type HasCoolness<T extends { [key: string]: unknown }> = T extends { coolness: infer R } ? R : never
type TestA = HasCoolness<{ coolness: number }> // number
type TestB = HasCoolness<{ coolness: string }> // string
type TestC = HasCoolness<{ notCool: 'ugh' }> // never
Run Code Online (Sandbox Code Playgroud)
该HasCoolness类型推断属性的值coolness并返回其类型。如果没有属性,never则返回,表明返回的类型不可使用。
所以这一切都意味着这正在做:
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
? Return
: never
Run Code Online (Sandbox Code Playgroud)
同样的事情,只不过不是推断属性,而是推断函数的返回类型。请注意,因为Type不受约束(即它不是GetReturnType<Type extends SomeOtherType>),所以您可以将任何内容传递给它。但是,如果您传递的东西不是函数,它就不会适合该模式,并且将使用三元组的否定子句,这将导致never.
| 归档时间: |
|
| 查看次数: |
3889 次 |
| 最近记录: |