TypeScript extends

Posted Himmelbleu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TypeScript extends相关的知识,希望对你有一定的参考价值。

extends 的含义

extends 在接口和类中都是继承的意思,继承之后的子接口或子类都是属于这个父接口或父类的,是一个从属关系。

extends 还有一个重要的用法就是在类型中,依旧还是继承的意思,是一种从属关系。如下的例子所示:

interface Animal 
    name: string;
    age: number;


interface Dog extends Animal 
    run: () => void;


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

判断 Dog 是否属于 Animal 类型,如果属于就返回 string 类型给 A,否则返回 number 给 A。Dog 接口继承了 Animal 接口,所以 Dog 属于 Animal,因此,A 得到的是一个 string 类型。

下面还有一个例子,可能反直觉:

interface Animal 
    name: string;
    age: number;


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


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

A 得到的是一个 string 类型。接口不管你有没有使用 extends 继承,只要 Dog 接口包括了 Animal 接口的全部属性,Dog 就是属于 Animal 的,因此 A 得到的是一个 string。

分配条件类型

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

A3 得到的类型是 string | number 联合类型,这是因为分配条件类型

在条件类型判断的情况下(上边示例中出现的 extends),如果入参是联合类型,则会被拆解成一个个独立的类型进行类型运算,每一个独立的运算的结果处理成一个联合类型。

A3 的联合类型可以看作下面这样的运算得到:

type A3 = (\'x\' extends \'x\' ? string : number) | (\'y\' extends \'x\' ? string : number);
//   ^? string | number

给 P 类型传递的参数类型 T,即 \'x\' | \'y\',满足“分配条件类型”的规则,因此 A3 得到的是一个 string | number 联合类型。

阻止分配条件类型

阻止发生“分配条件类型”,可以对参数类型和 extends 后面的类型都加上 []

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

never 类型

还是上面的例子,左边的类型换成 never:

type A1 = never extends \'x\' ? string : number;
//   ^? string

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

A1 不符合“分配条件类型”规则,never 可以是 \'x\' 的子类型,因此,A1 得到的类型是 string。

A2 符合“分配条件类型”规则,参数类型 T 是 never 类型,因此,A2 得到的是一个 never 类型。

阻止分配条件类型

阻止发生“分配条件类型”,可以对参数类型和 extends 后面的类型都加上 []

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

A2 不符合“分配条件类型”规则,never 可以是 \'x\' 的子类型,因此,A2 得到的类型是 string。

白话typescript中的extends和infer(含vue3的UnwrapRef)





































































































































大家好,我是小雨小雨,致力于分享有趣的、实用的技术文章。




内容分为翻译和原创,如果有问题,欢迎随时评论或私信,希望和大家一起进步。




分享不易,希望能够得到大家的支持和关注。



extends


typescript 2.8引入了条件类型关键字: extends,长这个样子:


T extends U ? X : Y

看起来是不是有点像三元运算符: condition ? result(1) : result(2),用大白话可以表示为:



如果T包含的类型 是 U包含的类型的 ‘子集‘,那么取结果X,否则取结果Y



再举几个ts预定义条件类型的例子,加深理解:


type NonNullable<T> = T extends null | undefined ? never : T;

// 如果泛型参数 T 为 null 或 undefined,那么取 never,否则直接返回T。
let demo1: NonNullable<number>; // => number
let demo2: NonNullable<string>; // => string
let demo3: NonNullable<undefined | null>; // => never

分配式extends


T extends U ? X : Y

其实就是当上面的T为联合类型的时候,会进行拆分,有点类似数学中的分解因式:



(a + b) * c ? ac + bc



再举个官网的例子:


type Diff<T, U> = T extends U ? never : T; // 找出T的差集
type Filter<T, U> = T extends U ? T : never; // 找出交集

type T30 = Diff<‘a‘ | ‘b‘ | ‘c‘ | ‘d‘, ‘a‘ | ‘c‘ | ‘f‘>; // => ‘b‘ | ‘d‘
// <‘a‘ | ‘b‘ | ‘c‘ | ‘d‘, ‘a‘ | ‘c‘ | ‘f‘>
// 相当于
// <‘a‘, ‘a‘ | ‘c‘ | ‘f‘> |
// <‘b‘, ‘a‘ | ‘c‘ | ‘f‘> |
// <‘c‘, ‘a‘ | ‘c‘ | ‘f‘> |
// <‘d‘, ‘a‘ | ‘c‘ | ‘f‘>
type T31 = Filter<‘a‘ | ‘b‘ | ‘c‘ | ‘d‘, ‘a‘ | ‘c‘ | ‘f‘>; // => ‘a‘ | ‘c‘
// <‘a‘ | ‘b‘ | ‘c‘ | ‘d‘, ‘a‘ | ‘c‘ | ‘f‘> 同上

let demo1: Diff<number, string>; // => number

我们再来看看infer。


infer


在extends语句中,还支持infer关键字,可以推断一个类型变量,高效的对类型进行模式匹配。但是,这个类型变量只能在true的分支中使用。


// 内置 ReturnType
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

不知道初学ts的朋友们看完这个介绍是不是一脸懵逼,反正之前我是...


其实理解之后很简单,这里直接说下我的理解,应该还算简单易懂:



infer X 就相当于声明了一个变量,这个变量随后可以使用,是不是有点像for循环里面的声明语句?



for (let i = 0, len = arr.length; i < len; i++) {
// do something
}


不同的是,infer X的这个位置本应该有一个写死的类型变量,只不过用infer R替换了,更灵活。




需要注意的是infer声明的这个变量只能在true分支中使用



还是举几个例子,加深理解,纸上谈兵终觉浅嘛:


例子一


// 解读: 如果泛型变量T是 () => infer R的`子集`,那么返回 通过infer获取到的函数返回值,否则返回boolean类型
type Func<T> = T extends () => infer R ? R : boolean;

let func1: Func<number>; // => boolean
let func2: Func<‘‘>; // => boolean
let func3: Func<() => Promise<number>>; // => Promise<number>

例子二


// 同上,但当a、b为不同类型的时候,返回不同类型的联合类型
type Obj<T> = T extends {a: infer VType, b: infer VType} ? VType : number;

let obj1: Obj<string>; // => number
let obj2: Obj<true>; // => number
let obj3: Obj<{a: number, b: number}>; // => number
let obj4: Obj<{a: number, b: () => void}>; // => number | () => void

例子三(Vue3中的UnwrapRef)


// 如果泛型变量T是ComputedRef的‘子集‘,那么使用UnwrapRefSimple处理infer指代的ComputedRef泛型参数V
// 否则进一步判断是否为Ref的‘子集‘,进一步UnwrapRefSimple
export type UnwrapRef<T> = T extends ComputedRef<infer V>
? UnwrapRefSimple<V>
: T extends Ref<infer V> ? UnwrapRefSimple<V> : UnwrapRefSimple<T>

// 我是分割线

// 如果T为Function | CollectionTypes | BaseTypes | Ref之一的‘子集‘,直接返回。
// 否则判断是否为数组的‘子集‘,不是的话视为object,调用UnwrappedObject
type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref
? T
: T extends Array<any> ? T : T extends object ? UnwrappedObject<T> : T

// 我是分割线
// 调用UnwrapRef,产生递归效果,解决了ts类型递归
type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>

// 我是分割线

// 泛型Ref
export interface Ref<T = any> {
[Symbol()]: true
value: T
}

// 我是分割线

export interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly value: T
}

// 我是分割线

export interface WritableComputedRef<T> extends Ref<T> {
readonly effect: ReactiveEffect<T>
}

建议自己捋一遍。


总结


ts提供的extends和infer大大增加了类型判断的灵活性和复用性,虽然用与不用都可以,但能熟练地使用高级特性将大大提升ts推断的效率和代码类型的可读性。


如有问题,欢迎指出。


劳动节快乐!


技术图片

以上是关于TypeScript extends的主要内容,如果未能解决你的问题,请参考以下文章

[Vue @Component] Extend Vue Components in TypeScript

白话typescript中的extends和infer(含vue3的UnwrapRef)

白话typescript中的extends和infer(含vue3的UnwrapRef)

TypeScript 基础学习之泛型和 extends 关键字

typescript使用 TypeScript 开发 Vue 组件

在vue项目中写Typescript