🚀🚀🚀Typescript通关秘籍(五)🔥🔥🔥

Class

class 的基本语法我们就不探究了,classTS 中是全面支持的,我们主要关注 TS 对于类属性和方法的类型声明方式即可。

对于类属性,有两种声明类型的方式:

javascript 复制代码
class Person {
  name: string; // 1. 直接在顶层声明时顺便给出类型(推荐)
  age: number = 18; // 2. 通过类型推断
  constructor(name: string) {
    this.name = name;
  }
}

类属性在 TS 中默认要求有初始值,也可以通过 strictPropertyInitialization 配置项去关闭。

对于类方法,它与普通函数的类型声明方式一致。

javascript 复制代码
class Person {
  name = '橙某人';
  constructor(name: string) {
    this.name = name;
  }
  say(newName: string): void {
    console.log(`my name is ${this.name}`);
  }
}

implements

implements 表示实现,一个新类必须实现类型别名、接口或者父类的所有属性和方法。

javascript 复制代码
type P = {
  name: string;
  age: number;
  say: (newName: string) => void;
}

/* or
interface P {
  name: string;
  age: number;
  say: (newName: string) => void;
}
*/

class Person implements P {
  name;
  age = 18;
  constructor(name: string) {
    this.name = name;
  }
  say(newName: string) {
    console.log(`my name is ${this.name}`);
  }
}

需要注意,类方法参数的类型声明不能省略,也不能改成其他类型,否则参数类型会变成 any 类型或者报错。😕

javascript 复制代码
class Person implements P {
  say(newName) { // any
  }
}
class Person implements P {
  say(newName: number) { // ❌
  }
}

多重实现

类可以实现多重限制,每个限制之间使用逗号隔开。

javascript 复制代码
type P1 = {
  name: string;
}
interface P2 {
  age: number;
}
class P3 {
  say(newName: string): void {}
}

class Person implements P1, P2, P3 {
  name = '橙某人';
  age = 18;
  say(newName: string) {
    console.log(`my name is ${this.name}`);
  }
}

实现多重限制时,不同限制之间不能有冲突的属性。

实例类型声明

声明类实例的类型有两种方式:

javascript 复制代码
type P1 = {
  name: string;
}
interface P2 {
  age: number;
}
class P3 {
  say(newName: string): void {}
}

class Person implements P1, P2, P3 {
  name = '橙某人';
  age = 18;
  say(newName: string) {
    console.log(`my name is ${this.name}`);
  }
}

let p1: Person = new Person(); // 推荐
let p2: P1 = new Person();

p2.name // ✅
p2.age // ❌

修饰符

readonly

readonlyTS 新增的一个关键字,表示只读,不可能被修改。

它的出现主要是为了解决 const 声明的对象,所指堆空间对象依旧可变的不足。

列了一些使用场景:

javascript 复制代码
// 对象
let obj: {
  readonly name: string;
  readonly age: number;
} = {
  name: '橙某人',
  age: 18
}
obj.name = ''; // ❌


// 函数
function fn(obj: { readonly name: string; readonly age: number; }) {
  obj.name = ''; // ❌
}

// 数组
let arr: readonly number[] = [1, 2, 3];
arr.push(4); // ❌ 

// 元组
let tArr: readonly [number, string] = [1, 'a'];
tArr.push(4); // ❌

// type
type Obj = {
  readonly name: string;
  readonly age: number;
}
let o: Obj = {
  name: '橙某人',
  age: 18
}
o.name = ''; // ❌

// interface
interface Person {
  readonly name: string;
  readonly age: number;
}
let oo: Person = {
  name: '橙某人',
  age: 18,
}
oo.name = ''; // ❌

// class
class Student {
  readonly name: string = '橙某人';
  readonly age: number;
  constructor() {
    this.age = 18; // 这里只是初始化而已,不是修改❗
  }
  say() {
    this.name = ''; // ❌
  }
}

随便看看就得了。👻

泛型

泛型(Generics)可以理解成一个占位的未知"类型参数",具体这个类型参数是什么类型需要在使用的时候才能确定下来。

来看个例子:

javascript 复制代码
function numToArray(a: number, b: number): number[] {
  return [a, b];
}
function strToArray(a: string, b: string): string[] {
  return [a, b];
}

let numArray = numToArray(1, 2); // number[]
let strArray = strToArray('a', 'b'); // string[]

上述两个函数,在 JS 中完全能被写成一个,但是在 TS 中却需要拆成两个函数,只因参数和返回值类型的不一致导致。

虽然也能用函数重载来解决😏,但...这不重要不重要。

而这种场景,利用泛型就能很好得到解决。

javascript 复制代码
function fn<T>(a: T, b: T): T[] {
  return [a, b];
}

let numArray = fn<number>(1, 2); // number[]
let strArray = fn<string>('a', 'b'); // string[]

是不是能感受到泛型的灵活和强大了?💪

当然,泛型的类型参数可以有多个,通过逗号隔开。

javascript 复制代码
function fn<T, K, V>(a: T, b: K, c: V): (T | K | V)[] {
  return [a, b, c];
}

let res = fn<number, string, boolean>(1, 'a', true); // (string | number | boolean)[]

T, K, V 这些参数命名与函数参数名一样,可以由你随意定义。

四种应用场景

泛型主要用在四个场景:函数、类型别名、接口、class

函数

javascript 复制代码
function fn1<T>(args: T): T {
  return args;
}
fn1<number>(1);

let fn2: <T>(args: T) => T = args => args;
fn2<number>(1);

let obj: {
  fn3: <T>(args: T) => T;
} = {
  fn3: args => args
};
obj.fn3<number>(1);

类型别名

javascript 复制代码
type ArticleName<T> = string | number | T;
type Person<T, K> = {
  name: T;
  age: K;
}

let an1: ArticleName<string> = '橙某人';
let an2: ArticleName<number> = 999;
let an3: ArticleName<boolean> = true;

接口

javascript 复制代码
interface Person<T, K> {
  name: T;
  age: K;
}

let person: Person<string, number> = {
  name: '橙某人',
  age: 18
}

函数的泛型接口可以有两种形式:

javascript 复制代码
// 在定义时传递类型
interface Fn1<T> {
  (args: T): T;
}
let fn4: Fn1<number> = args => args;
fn4(1);

// 在调用函数的时候传递类型
interface Fn2 {
  <T>(args: T): T;
}
let fn5: Fn2 = args => args;
fn5<number>(1);

class

javascript 复制代码
class Person<T, K> {
  name: T;
  constructor(name: T) {
    this.name = name;
  }
  say(args: K): K {
    return args;
  }
}

let p = new Person<string, number>('橙某人');

默认值

泛型的类型参数也可以设置默认值,当我们没有传递类型时,就会使用默认值。

javascript 复制代码
type ArticleName<T = string> = T;

let name1: ArticleName<number> = 1; // number
let name2: ArticleName; // string

默认值有两个注意点:

  • 默认值经常会被类型推断给覆盖。
  • 多个泛型的类型参数,具有默认值的类型参数必须放置到最后面。

用上面的例子来看:

javascript 复制代码
function fn<T = string>(a: T, b: T): T[] {
  return [a, b];
}

let numArray = fn(1, 2); // number[]
let strArray = fn('a', 'b'); // string[]

它也能简写,不传类型,让 TS 自行去进行类型推断,结果是一样的。

同时,虽然没有传递类型,但是默认值(T = string)也没有生效,说明类型推断的优先级是高于默认值的。

如果有多个类型参数,具有默认值的类型参数必须在最后面。

javascript 复制代码
function fn1<T = string, U>(){} // ❌

function fn2<U, T = string>(){} // ✅
function fn3<U, T = string, K = number>(){} // ✅

泛型约束-extends

泛型的强大和灵活相信你能有所体会,但是有利就有弊。

来看下面的例子:

从入参我们可以明确知道参数会有 length 属性,但是我们想打印它却不行❗

泛型约束 的存在就是为了解决这类问题的。

泛型约束使用 extends 关键字来完成,它能帮我们提前告诉 TS,泛型的类型参数可能存在"某些类型"。

javascript 复制代码
function fn<T extends { length }>(args: T) {
  console.log(args.length); // ✅
}
fn<string>('abc');
fn<number[]>([1, 2, 3]);

fn<number>(123); // ❌

或者

javascript 复制代码
interface Len{
  length: number
}
function fn<T extends Len>(args: T) {
  console.log(args.length); // ✅
}
fn<string>('abc');
fn<number[]>([1, 2, 3]);

fn<number>(123); // ❌

再多列几个例子体验体验:

javascript 复制代码
// 1
type Name<T extends string> = T;
let name1: Name<string> = '橙某人';
let name2: Name<number> = 1; // ❌

// 2
type Obj<T extends { name: string }> = T;
let obj1: Obj<{ name: string, age: number }> = {
  name: '橙某人',
  age: 18,
};
let obj2: Obj<{ age: number }> = {}; // ❌

// 3
type Obj<T extends U, U> = T;
let obj3: Obj<{ name: string, age: 18 }, { name: string }> = {
  name: '橙某人',
  age: 18,
}
let obj4: Obj<{ name: string }, { name: string, age: 18 }> = {} // ❌

从这些例子中,能大概瞧出(T extends UT 必须要满足 U 才行,否则就会报错。

泛型约束在一些地方也被叫做泛型条件类型,因为它和我们熟知的三元表达式(?:)很相像。

泛型约束这个概念不管在官网还是在网上的一些文章中,小编都没有一个很好的总结,大部分都是通过例子来说明。如您有更好的说明,希望、期待你在评论区中告诉小编,感谢感谢。😀😀😀

内置的泛型工具类型

泛型虽好,但不可贪多。

泛型虽然强大灵活,但是会加大代码的复杂性,可读性大大降低。

同时,泛型的编写难度门槛也是相当之高。

为此,TS 专门内置了一些常用的泛型工具类型,就像我们常用的 JS 工具方法一样。

Partial

Partial<T>:将声明的对象类型所有属性变成可选的。

示例

javascript 复制代码
interface Person {
  name: string;
  age: number;
}
type P = Partial<Person>;

// 等价于
type P = {
  name?: string;
  age?: number;
}

源码实现

javascript 复制代码
type Partial<T> = {
  [P in keyof T]?: T[P];
};

Required

Required<T>:将声明的对象类型所有属性变成必填的。

示例

javascript 复制代码
interface Person {
  name?: string;  
  age?: number;
}
type P = Required<Person>;

// 等价于
type P = {
  name: string;
  age: number;
}

源码实现

javascript 复制代码
type Required<T> = {
  [P in keyof T]-?: T[P];
};

Readonly

Readonly<T>:将声明的对象类型所有属性变成只读的。

示例

javascript 复制代码
interface Person {
  name: string;  
  age?: number;
}
type P = Readonly<Person>;

// 等价于
type P = {  
  readonly name: string;  
  readonly age?: number;  
}

源码实现

javascript 复制代码
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

Pick

Pick<T, K>:从一个声明好的对象类型中,挑取部分属性出来组成一个新的对象类型。

示例

javascript 复制代码
interface Person {
  readonly name: string;  
  age?: number;
  sex: boolean
}
type P = Pick<Person, 'name' | 'age'>;

// 等价于
type P = {  
  readonly name: string;  
  age?: number; 
}

源码实现

javascript 复制代码
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

Omit

Omit<T, K>:从一个声明好的对象类型中,去除部分属性,剩下的组成一个新的对象类型。(与 Pick 相反)

示例

javascript 复制代码
interface Person {
  name: string;  
  age?: number;
  sex: boolean
}
type P = Omit<Person, 'name' | 'sex'>;

// 等价于
type P = {  
  age?: number;  
}

源码实现

javascript 复制代码
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Record

Record<K, T>:将所有的 K 属性变成 T 类型,返回一个对象类型。

K 属性只能是 string | number | symbol

示例

javascript 复制代码
type Person = 'name' | 'age';
type P = Record<Person, string>;

// 等价于
type P = {  
 name: string;  
 age: string;  
}

源码实现

javascript 复制代码
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

Exclude

Exclude<T, U>:将 T 类型中属于 U 类型的部分移除。

示例

javascript 复制代码
type Person1 = string | number | boolean;
type Person2 = string;
type P = Exclude<Person1, Person2>;

// 等价于
type P = number | boolean;

值类型

javascript 复制代码
type Person1 = '橙某人' | 18 | true;
type Person2 = '橙某人';
type P = Exclude<Person1, Person2>;

// 等价于
type P = 18 | true;

源码实现

javascript 复制代码
type Exclude<T, U> = T extends U ? never : T;

😮这个其实挺有意思的,从源码中完全看不出是这个效果呢。。。猜想应该是 never 的效果,还没去细细探究过,小编期待你的评论。😖

Extract

Extract<T, U>:将 T 类型与 U 类型中相同部分提取出来,组成一个新类型。(与 Exclude 相反)

示例

javascript 复制代码
type Person1 = string | number | boolean;
type Person2 = string;
type P = Extract<Person1, Person2>;

// 等价于
type P = string;

源码实现

javascript 复制代码
type Extract<T, U> = T extends U ? T : never;

ReturnType

ReturnType<T>:获取函数类型的返回值类型。

示例

javascript 复制代码
type Fn = (args: number) => string;
type P = ReturnType<Fn>; // string

type P1 = ReturnType<() => string>; // string
type P2 = ReturnType<() => void>; // void
type P3 = ReturnType<<T>() => T>; // unknown
type P4 = ReturnType<any>; // any
type P5 = ReturnType<never>; // never
type P6 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]

源码实现

javascript 复制代码
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

Parameters

Parameters<T>:获取函数类型的参数类型,返回一个元组。

示例

javascript 复制代码
type Fn = (a: number, b: string) => void;
type P = Parameters<Fn>;

// 等价于
type P = [a: number, b: string]

源码实现

javascript 复制代码
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

以上小编列举了常见的十个泛型工具类型以供参考。

更多工具类型可以上源码中查阅。传送门

一些高级的工具类型的解析可以参考这篇文章。传送门


至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。

老样子,点赞+评论=你会了,收藏=你精通了。

相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax