TypeScript实战系列之ts高手必备技能(最终篇)

本文介绍了TypeScript中的关键字如keyof、typeof、infer和条件类型,以及MappingModifiers和TemplateLiteralTypes的使用,展示了如何创建类型工具如PartialByKeys和FunctionProperties,帮助开发者在日常开发中灵活处理类型规则。
摘要由CSDN通过智能技术生成

介绍

ts 中 有一组关键字 (keyof typeof infer extends in as ) 用于利用已有的类型生成新的类型。
ts中知识比较多, 这里只介绍个人觉得日常开发需要用到的部分。

先来个开胃菜 下面这个类型的作用

type PartialByKeys<T extends { [p: string]: any }, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
} & {
  [P in K]?: T[P];
};

interface User {
  name: string;
  age: number;
  email: string;
}
// 示例用法
type PartialUser = PartialByKeys<User, "name" | "age">;

const user = {
  email: "123",
};

作用就是 将类型指定的键的属性变为可选属性
在这里插入图片描述
学完这一篇,你就能随性所欲的来封装属于你的类型工具!

keyof 类型运算符

keyof 关键字用于获取一个对象的所有可枚举属性的键的类型


type People = {
    name:string;
    age:number;
}

type DD = keyof People //  "name" | "age"


type ArrayObj = { [n: number]: unknown };
type A = keyof ArrayObj;// number

type MapObj = { [k: string]: boolean };
type M = keyof MapObj;  // string | number
// M 是 string | number — 这是因为 JavaScript 对象键始终强制转换为字符串, obj[0] 等价于 obj["0"]

// 特殊 用途

type B  = keyof any //string | number | symbol

typeof 类型运算符

使用 typeof 运算符来获取一个变量或表达式的类型

// 获取基本类型的类型
let myNumber: number = 10;
type MyNumberType = typeof myNumber; // number


let myString: string = "Hello, World!";
type MyStringType = typeof myString; // string


// 获取对象和数组的类型
let myObject = { key: "value" };
type MyObjectType = typeof myObject; // {key:string}


let myArray = [1, 2, 3];
type MyArrayType = typeof myArray; // number[]


// 获取函数的类型
function myFunction() {}
type MyFunctionType = typeof myFunction; // ()=> void

通过索引获得类型

可以使用索引访问类型来查找另一种类型的特定属性

type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];// number

type I1 = Person["age" | "name"];
     
type I1 = string | number
 
type I2 = Person[keyof Person];
     
type I2 = string | number | boolean
 
type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];
     
type I3 = string | boolean

使用 number 来获取数组元素的类型


type PersonType = Array<string|number>
   
type Person = PersonType[number] // string|number

Conditional Types 条件类型(重点)

Conditional Types 形式看起来有点像 js 中的条件表达式 ( condition ? trueExpression : falseExpression )
ts 主要使用 extends 关键字


interface IdLabel {
  id: number /* some fields */;
}
interface NameLabel {
  name: string /* other fields */;
}

// 意思是 接受一个类型 被限制在 number 或者 string 如何 是 number 就 返回 IdLabel 类型 string 就返回 NameLabel
type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;

  type a = NameOrId<number> // IdLabel
  type b = NameOrId<string> // NameLabel

infer 关键字 (重中之重)

ts 2.8 版本 新增 关键字 infer. 用于在extends的条件语句中推断待推断的类型。
参考链接:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html

// 使用infer来推断函数的返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : T;

type func = () => number;
type variable = ()=>string;
type funcReturnType = MyReturnType<func>; // funcReturnType 类型为 number
type varReturnType = MyReturnType<variable>; // varReturnType 类型为 string

infer 可以理解为占位符

type Unpacked<T> = T extends (infer U)[]
  ? U
  : T extends (...args: any[]) => infer U
  ? U
  : T extends Promise<infer U>
  ? U
  : T;

  
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string

协变位置中同一类型变量的多个候选者如何导致推断出联合类型

type ElementOf<T> = T extends Array<infer R> ? R : never;

type TTuple = [string, number];
type Union = ElementOf<TTuple>; // string | number

逆变位置中同一类型变量的多个候选会导致推断交集类型

type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
  ? U
  : never;
type T1 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // string

/**
 * {
    age: number;
} & {
    address: string;
}
 */
type T2 = Bar<{ a: (x: {age:number}) => void; b: (x: {address:string}) => void }>; 

在条件类型中,如果入参是联合类型,则会被拆解为一个个独立的(原子)类型(成员),然后再进行类型运算。

type Diff<T, U> = T extends U ? never : T; // Remove types from T that are assignable to U
type Filter<T, U> = T extends U ? T : never; // Remove types from T that are not assignable to U
type T0 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type T1 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
type T2 = Diff<string | number | (() => void), Function>; // string | number
type T3 = Filter<string | number | (() => void), Function>; // () => void

条件类型与映射类型结合使用

type FunctionPropertyNames<T> = {
    [K in keyof T]: T[K] extends Function ? K : never;
  }[keyof T];
  type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
  type NonFunctionPropertyNames<T> = {
    [K in keyof T]: T[K] extends Function ? never : K;
  }[keyof T];
  type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
  interface Part {
    id: number;
    name: string;
    subparts: Part[];
    updatePart(newName: string): void;
  }
  type T0 = FunctionPropertyNames<Part>; // "updatePart"
  type T1 = NonFunctionPropertyNames<Part>; // "id" | "name" | "subparts"
  type T2 = FunctionProperties<Part>; // { updatePart(newName: string): void }
  type T3 = NonFunctionProperties<Part>; // { id: number, name: string, subparts: Part[] }

as 和 in 关键字 在 循环里 配合 条件语句使用

as 是 4.1 版本后才能使用
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html

作用: 在 as 后面 遍历的变量类型 可以配合 条件语句使用

//那么如何定义一个 ConditionalPick 工具类型,支持根据指定的 Condition 条件来生成新的类型,对应的使用示例如下:
interface Example {
	a: string;
	b: string | number;
	c: () => void;
	d: {};
}

type ConditionalPick<T,K extends T[keyof T]> = {
     [P in keyof T as T[P] extends K ? P:never ]: T[P] 
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}

Mapping Modifiers 映射修改器(重点)

在映射过程中可以应用两个附加修饰符: readonly 和 ? ,它们分别影响可变性和可选性。可以通过添加 - 或 + 前缀来删除或添加这些修饰符。如果不添加前缀,则假定为 + 。

例子 : 去除 readonly 只读

type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
  readonly id: string;
  readonly name: string;
};

type UnlockedAccount = CreateMutable<LockedAccount>;

去除 ? 可选

type Concrete<Type> = {
    [Property in keyof Type]-?: Type[Property];
  };
   
  type MaybeUser = {
    id: string;
    name?: string;
    age?: number;
  };
   
  type User = Concrete<MaybeUser>;

在 TypeScript 4.1 及更高版本中,可以使用映射类型中的 as 子句重新映射映射类型中的键

type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
 
interface Person {
    name: string;
    age: number;
    location: string;
}
 
type LazyPerson = Getters<Person>;

可以通过条件类型生成 never 来过滤键(重点)

去除 kind 属性

// Remove the 'kind' property
type RemoveKindField<T> = {
    [P in keyof T as P extends 'kind' ? never : P]: T[P]
};
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type KindlessCircle = RemoveKindField<Circle>;

Template Literal Types 模板文字类型

type World = "world";
type Greeting = `hello ${World}`; //"hello world"

开发中的应用
限制 方法入参的类型规则

type PropEventSource<Type> = {
  on<Key extends string & keyof Type>(
    eventName: `${Key}Changed`,
    callback: (newValue: Type[Key]) => void
  ): void;
};

function makeWatchedObject<Type extends { [p: string]: any }>(obj: Type) {
  return {
    ...obj,
    on:
      <PropName extends string & keyof Type>(propName: `${PropName}Changed`) =>
      (callback: (newValue: Type[PropName]) => void) => {
        // 在这里可以添加对特定属性的监听逻辑
      },
  } as Type & PropEventSource<Type>;
}

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26,
});

person.on("firstNameChanged", (newName) => {
  console.log(`new name is ${newName.toUpperCase()}`);
});

person.on("ageChanged", (newAge) => {});

开胃菜 解析

type PartialByKeys<T extends { [p: string]: any }, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
} & {
  [P in K]?: T[P];
};

interface User {
  name: string;
  age: number;
  email: string;
}
// 示例用法
type PartialUser = PartialByKeys<User, "name" | "age">;

const user = {
  email: "123",
};

我们需要实现 类型指定的键的属性变为可选属性

  1. 需要接受2个泛型参数 一个是 键值对类型的 一个需要变成可选属性的键名 ,再给 这两个泛型加上限制条件
// T 只能接受 键值 是string 类型的对象 ,K 只能是 T类型的键名
type PartialByKeys<T extends { [p: string]: any }, K extends keyof T> = {}
  1. 需要对 T 的 键值 进行遍历处理 先找出不需要处理的键(涉及到过滤不符合条件的键名 需要 配合条件语句 设置为never 来 忽略掉)
// 现在已经将 不需要处理的键值对 取出来
type PartialByKeys<T extends { [p: string]: any }, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
} 
  1. 再使用交互类型 将剩余需要处理成可选的键 一起返回
type PartialByKeys<T extends { [p: string]: any }, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
} & {
  [P in K]?: T[P];
};

总结

以上基本涵盖了90% ts 的使用。配合类型体操练习题 多练练 ts就算ok了。

下面附上 刷题地址
https://github.com/type-challenges/type-challenges/issues

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值