/ts-challenges

ts类型体操

Primary LanguageTypeScript

ts-challenges

ts 类型体操

extends 条件判断

T extends U ? X : Y // 如果 T 包含的类型 是 U包含的类型的 '子集',那么取结果X,否则取结果Y

T 满足 U, 你需要的我有, 我就满足你
interface Animal {
  eat(): void;
}

interface Dog extends Animal {
  bite(): void;
}

// extends 类似于 三元表达式, 所以 A的类型为string
type A = Dog extends Animal ? string : number; // type A = string

// extends判断条件真假的逻辑是什么?
上面的例子: Dog  Animal 的子类, Dog 类型的值可以赋值给 Animal 类型的值, 判断为真

--------

interface A1 {
  name: string;
}

interface A2 {
  name: string;
  age: number;
}

// A 的类型为 string
type A = A2 extends A1 ? string : number;

const a: A = "this is string";

上面的例子: A1,A2两个接口,满足A2的接口一定可以满足A1,所以条件为真,A的类型取string

-----

### 泛型用法

type A1 = "x" extends "x" ? string : number; // string
type A2 = "x" | "y" extends "x" ? string : number; // number

// P是带参数T的泛型类型
type P<T> = T extends "x" ? string : number;

// A3是泛型类型P传入参数'x' | 'y'得到的类型,如果将'x' | 'y'带入泛型类的表达式
type A3 = P<"x" | "y">; // string | number

// 分配条件类型(Distributive Conditional Types)

对于使用 extends 关键字的条件类型(即上面的三元表达式类型),如果 extends 前面的参数是一个 泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果

分配律是指,将联合类型的联合项拆成单项,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果

extends的前参为 T,T是一个泛型参数。在 A3 的定义中,给 T 传入的是'x''y'的联合类型'x' | 'y',满足分配律,于是'x''y'被拆开,分别代入P<T>

P<'x' | 'y'> => P<'x'> | P<'y'>

'x' extends 'x' ? string : number => string
'y' extends 'x' ? string : number => number

然后将每一项代入得到的结果联合起来,得到 => string | number

满足两个要点即可适用分配律: 第一: 参数是泛型类型, 第二: 代入参数的是联合类型


### 特殊的 never

// never是所有类型的子类型
type A1 = never extends "x" ? string : number; // string

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

never 被认为是空的联合类型, 所以P<T>的表达式其实根本就没有执行,所以A2的定义也就类似于永远没有返回的函数一样,是never类型的

### 防止条件判断中的分配

type P<T> = [T] extends ["x"] ? string : number;
type A1 = P<"x" | "y">; // number
type A2 = P<never>; // string

条件判断类型的定义中,将泛型参数使用 [] 括起来,即可阻断条件判断类型的分配,此时,传入参数T的类型将被当做一个整体,不再分配
### 在高级类型中的应用

Exclude 是TS中的一个高级类型,其作用是从第一个联合类型参数中,将第二个联合类型中出现的联合项全部排除,只留下没有出现过的参数

type Exclude<T, U> = T extends U ? never : T

type A = Exclude<'key1' | 'key2', 'key2'> // 'key1'
==>
type A = `Exclude<'key1', 'key2'>` | `Exclude<'key2', 'key2'>`
==>
type A = ('key1' extends 'key2' ? never : 'key1') | ('key'2 extends 'key2' ? never : 'key2')


Extract 将第二个参数的联合项从第一个参数的联合项中提取出来,当然,第二个参数可以含有第一个参数没有的项
type Extract<T, U> = T extends U ? T : never

type A = Extract<'key1' | 'key2', 'key1'> // 'key1'

Pick extends的条件判断,除了定义条件类型,还能在泛型表达式中用来约束泛型参数

// 高级类型Pick的定义
type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}

interface A {
    name: string;
    age: number;
    sex: number;
}

type A1 = Pick<A, 'name'|'age'>
// 报错:类型“"key" | "noSuchKey"”不满足约束“keyof A”
type A2 = Pick<A, 'name'|'noSuchKey'>

K extends keyof T 则是用来约束K的条件,即,传入K的参数必须使得这个条件为真,否则ts就会报错,也就是说,K的联合项必须来自接口T的属性

泛型

/**
 * T => Type; K => Key; V => Value; E => Element
 * @param value 类型不确定
 * @returns T
 */
function identity<T>(value: T): T {
  return value;
}

identity(1);

function identity<T, U>(value: T, message: U): T {
  console.log(message);
  return value;
}

identity<number, string>(12, "yym");
// 普通类型定义
type Duck<T> = { name: string; type: T };
// 普通类型使用
const duck: Duck<string> = { name: "鸭子", type: "animal" };

// 类定义
class Bird<T> {
  private type: T;
  constructor(type: T) {
    this.type = type;
  }
}
const bird: Bird<string> = new Bird<string>("animal");

// 函数定义
function swipe<T, U>(value: [T, U]): [U, T] {
  return [value[1], value[0]];
}
swipe<string, string>(["bird", "duck"]);

类型名 < 泛型列表 > 具体类型定义;

// 泛型推导和默认值
泛型名 = 默认类型;

// 泛型约束
function sum<T extends number>(value: T[]): number {
  let count = 0;
  value.forEach((v) => (count += v));
  return count;
}
// U 一定是 T 的 key 类型中的子集
function pick<T, U extends keyof T>() {}
泛型名 extends 类型


// 泛型条件
T extends U ? X : Y // 条件类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一, 这里便不限制 T 一定要是 U 的子类型,如果是 U 子类型,则将 T 定义为 X 类型,否则定义为 Y 类型
泛型名A extends 类型B ? 类型C : 类型D

// 泛型推断 infer
infer 的中文是 "推断" 的意思, 一般是搭配上面的泛型条件语句使用的,所谓推断,就是你不用预先指定在泛型列表中,在运行时会自动判断,不过你得先预定义好整体的结构
// {t: infer Test}可以看成是一个包含t属性的类型定义, 这个t属性的 value 类型通过infer进行推断后会赋值给Test类型, 如果泛型实际参数符合{t: infer Test}的定义那么返回的就是Test类型,否则默认给缺省的string类型
type Fo<T> = T extends { t: infer Test } ? Test : string;
type one = Fo<number>; // string
type two = Fo<{ t: boolean }>; // boolean
type three = Fo<{ a: number; t: () => void }>; // () => void

async function stringPromise() {
  return "Hello, Semlinker!";
}
interface Person {
  name: string;
  age: number;
}
async function personPromise() {
  return { name: "Semlinker", age: 30 } as Person;
}
type PromiseType<T> = (args: any[]) => Promise<T>;
type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;
type extractStringPromise = UnPromisify<typeof stringPromise>; // string
type extractPersonPromise = UnPromisify<typeof personPromise>; // Person


keyof any对应的类型为number | string | symbol,也就是用于做对象键(专业说法叫索引 index)的类型集合