首页 > 其他分享 >TypeScript 条件类型(Conditional Types)以及 infer 关键字

TypeScript 条件类型(Conditional Types)以及 infer 关键字

时间:2023-07-09 19:55:49浏览次数:38  
标签:TypeScript string Conditional number 泛型 extends 类型 type infer

什么是条件类型

条件类型可以让程序根据输入的类型来决定输出的类型是什么,也就是说根据不同的输入类型来确定输出的类型。

条件类型的形式有点类似于 JS 中的条件表达式(condition ? trueExpression : falseExpression):

file:[条件类型的规则]
SomeType extends OtherType ? TrueType : FalseType;

extends 含义

我直接把 extends 理解成继承(或从属)的意思。在条件类型中(Conditional Types)我甚至理解为是否等于的意思。

file:[extends 的含义]
interface Animal {
    name: string;
    age: number;
}

// Dog 接口继承于 Animal
interface Dog extends Animal {
    run: () => void;
}

// Dog 是否等于 Animal,或者 Dog 是否属于 Animal
type A = Dog extends Animal ? string : number
//   ^? string

在上面的一个条件类型中,很明显符合我认定的 extends 含义,Dog 是否属于 Animal,如果属于就返回 string,否则返回 number 类型。

反直觉的 extends

Animal 和 Dog 两个接口表面上没有任何的继承关系,相同点在于它们都有 name、age 属性且类型相同。

file:[反直觉的 extends]
interface Animal {
    name: string;
    age: number;
}

interface Dog {
    name: string;
    age: number;
    run: () => void;
}

type A = Dog extends Animal ? string : number
//   ^? string

A 得到的是一个 string 类型,这不说明 Dog 还是继承了 Animal 吗?但是,它们没有明确地通过 extends 关键字说明两者的关系。

因此,我得到一个结论:在 TypeScript 中无论接口是否通过 extends 指明继承关系,只要 Dog 接口包括 Animal 接口的全部内容,就可以被视作继承关系。但仅限于条件类型中。

条件类型

提取数组元素类型

file:[提取数组元素的类型]
type Flatten<T> = T extends unknown[] ? T[number] : T;

type Str = Flatten<string[]>;
    //^? string

type Num = Flatten<number>;
    //^? number

Flatten 判断泛型 T 是否属于数组类型,如果属于数组类型,就返回 T[number],它索引数组元素,并获得元素的类型。

这里很抽象的是 T[number],在具体的代码中我们索引一个数组的元素时,索引签名是数字,比如:

file:[索引数组类型的元素 并获取元素的值]

const arr = [1, 2, 3];

const item = arr[0]; // item => 1

在 TS 类型中也是一个道理,我们索引值 0 是一个具体的值,在 TS 类型中表示 number,把具体的代码抽象成 TS 来表示就是:T[number]。这个泛型 T 代表数组的数组类型,可能是数字数组、布尔数组、字符串数组。如下图所示,TS 类型与上面的具体代码:

索引 TS 数组类型的元素,并获取元素的类型

所以,类型 Flatten 就可以提取数组中元素的类型,并返回元素类型。

优化函数重载

file:[函数重载普通写法]
interface IdLabel {
  id: number;
}

interface NameLabel {
  name: string;
}

function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
  throw "unimplemented";
}

如果 createLabel 函数每一种情况都发生了变化,重载数量呈指数增长。取而代之的是,通过条件类型:

file:[通过条件类型优化函数重载]
type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;


function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
  throw "unimplemented";
}

我们可以使用这个条件类型将我们的重载简化为一个没有重载的单个函数。

infer

infer 提取数组元素类型

引入 infer 关键字,优化上面的 Flatten 类型别名,如下所示:

file:[infer 提取元素类型]
type Flatten<T> = T extends Array<infer I> ? I : T;

type Str = Flatten<string[]>
    //^? string

两者之间大差不差,区别就是,多了一个泛型 I,这个泛型 I 的最终结果由 infer 推断而来。

当传递 string[] 时,infer 关键字判断这个数组的元素类型是什么,很明显是 string 类型。因此,泛型 I 就是 string,而这个条件类型中符合 true 分支,所以,Flatten 返回的结果是泛型 I。

infer 提取函数返回值类型

在明白了 infer 关键字的意思之后,除了上述的作用以外,还可以提取函数的返回值类型。

file:[infer 提取函数返回值类型]
type GetReturnType<T> = T extends (...args: never[]) => infer R ? R: never;

type F1 = GetReturnType<() => string>;
    //^? string

type F2 = GetReturnType<(x: string, y: number) => number[]>;
   //^? number[]

infer R 中,目前不知道 R 的具体类型。我在 GetReturnType<() => string> 中传递了一个函数,其返回类型是 string。infer 就知道了 R 是一个 string 类型,所以 F1 就是 string 类型。

分配条件类型

file:[分配条件类型]
type A1 = 'x' extends 'x' ? string : number;
//   ^? string
type A2 = 'x' | 'y' extends 'x' ? string : number;
//   ^? number

type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'>
//   ^? string | number

在遇到联合类型的时候,条件类型和上面所展示的结果会不一样,具体表现在,extends 中左边如果是一个联合类型的时候,最终得到的是 false 条件的结果。

如上所示,A2 的结果是 number 类型,而不是 string 类型,extends 左侧是一个联合类型 'x' | 'y',明显都是 string 类型,结果却相反。

但是,当如果 extends 左侧是一个泛型,而在使用时给泛型传递的是一个联合类型,结果又不一样。

如上所示,定义了一个 P<T>,在 A3 中,我给 P 的泛型 T 传递的是一个联合类型,最终的结果是 string | number

阻止分配条件类型

针对以上的情况,在给泛型 T 传递联合类型时,在 extends 左侧使用 [] 把泛型包裹起来,就可以阻止结果是一个联合类型。

file:[阻止分配条件类型]
type P<T> = [T] extends ['x'] ? string : number;
type A3 = P<'x' | 'y'>
//   ^? number

never 的分配条件类型

file:[never 类型]
type A1 = never extends 'x' ? string : number;
//   ^? string

type P<T> = T extends 'x' ? string : number;
type A2 = P<never>
//   ^? never

never 类型可以是任何类型的子类型。因此,A1 是 string 类型。

同样的,我们给 extends 左侧是一个泛型,传递一个 never 给泛型,结果就是 never。如下所示,如果不想结果是一个 never,把泛型 T 包裹起来,结果就不是 never,而是 string。

file:[阻止结果是 never 类型]
type P<T> = [T] extends ['x'] ? string : number;
type A2 = P<never>
//   ^? string

标签:TypeScript,string,Conditional,number,泛型,extends,类型,type,infer
From: https://www.cnblogs.com/Himmelbleu/p/17539262.html

相关文章

  • TypeScript系列 3.接口和对象类型
    本系列知识部分基于小满ZS的TypeScript系列教程。我也会补充一些视频没有的内容。interface介绍interface即接口,在ts中用于描述对象的“形状”。js是鸭子类型,鸭子类型的通俗说法是:“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”只要一个对象包含interface中......
  • TypeScript系列 2.任意类型
    本系列知识部分基于小满ZS的TypeScript系列教程。我也会补充一些视频没有的内容。类型的等级层次任意类型any、unkownObject包装类型NumberStringBoolean基本类型numberstringboolean字面量never一条基本原则:等级低的能给等级高的赋值,等级高的不能给等级低的......
  • TypeScript+Vue3
    TypeScriptAny类型和unknown顶级类型1.没有强制限定哪种类型,随时切换类型都可以我们可以对any进行任何操作,不需要检查类型2.声明变量的时候没有指定任意类型默认为any3.弊端如果使用any就失去了TS类型检测的作用4.TypeScript3.0中引入的unknown类型也被认为是top......
  • TypeScript面向对象
    TypeScript面向对象面向对象是程序中一个非常重要的思想。面向对象很简单,简而言之就是程序之中所有的操作都需要通过对象来完成。一切皆对象接口TypeScript中的接口跟传统语言(比如Java)的接口有点差别对象可以隐式实现接口概念描述一个类型一个接口里面可以有:字段方法......
  • TypeScript基本介绍与开发环境
    TypeScript基本介绍与开发环境TypeScript官网TypeScript简介TypeScript是JavaScript的超集它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性TS代码需要通过编译器编译为JS,然后再交由JS解析器执行TS完全兼容JS,换言之,任何的JS代码都可以直接当成JS使用相较于J......
  • 记@ConditionalOnMissingBean注解导致bean注入失败的问题
    1.背景springboot项目,引入nacos做配置中心,pom.yaml导入依赖<dependency><groupId>com.alibaba.boot</groupId><artifactId>nacos-config-spring-boot-starter</artifactId><version>0.2.12</ver......
  • [Typescript] OverloadedReturnType & OverloadedParameters
    typeOverloadedReturnType<T>=Textends{(...args:any[]):inferR;(...args:any[]):inferR;(...args:any[]):inferR;(...args:any[]):inferR}?R:Textends{(...args:any[]):inferR;(...args:any[]):inferR;(...args:any......
  • 条件注解之@ConditionalOnProperty注解:通过配置文件的配置来控制配置类是否加入spring
    一、条件注解分类常见的@ConditionalOnxxx开头的注解我们称之为条件注解,常见的条件注解有class条件注解:@ConditionalOnClassbean条件注解:@ConditionalOnBean属性条件注解:@ConditionalOnProperty…@ConditionalOnProperty:如果有指定的配置,条件生效;@ConditionalOnBean:如果......
  • 关于 TypeScript 的变量声明和解构赋值(Destructuring Assignment)
    看下面这段代码:const{queryParams,fragment}=this.router.parseUrl(url);const[,path]=url.match(this.URL_SPLIT)??[,''];这段TypeScript代码虽然较短,但仍然展示了许多TypeScript的特性和语法。以下是对这段代码的分析,涵盖了相关的TypeScript特性和语法。......
  • 【论文阅读】CONDITIONAL POSITIONAL ENCODINGS FOR VISIONTRANSFORMERS
    来自美团技术团队2023年ICLR会议上发表的论文论文地址:https://link.zhihu.com/?target=https%3A//arxiv.org/pdf/2102.10882.pdf一、Motivation由于Transformer中的Self-Attention操作是Permutation-Invariant的,也就是说,对于同一个序列,任意顺序进行排列,Self-Attention得到的一......