【TypeScript】高级类型

文章目录

  • 1、交叉类型
  • 2、联合类型
  • 3、类型保护
    • 3.1 用户自定义的类型保护
    • 3.2 typeof 类型保护
    • 3.3 instanceof类型保护
  • 4、类型别名
    • 4.1 定义
    • - 接口 vs. 类型别名
  • 5、可辨识联合
  • 6、索引类型
    • 6.1 类型操作符:
      • - 索引类型查询操作符:keyof T
      • - 索引访问操作符:T[K]
    • 6.2 使用索引类型
  • 7、映射类型
    • in 操作符
    • 7.1 Partial 可选
      • 使用
      • 原理
    • 7.2 Readonly 只读
      • 使用
      • 原理
    • 7.3 Pick 抽取子集
      • 使用
      • 原理
    • 7.4 Record
      • 基本语法
      • 原理
    • 7.5 有条件的映射类型
      • 7.5.1 Exclude
      • 7.5.2 Extract
      • 7.5.3 NonNullable
      • 7.5.3 ReturnType
      • 7.5.3 InstanceType

1、交叉类型

交叉类型是将多个类型合并为一个类型
交叉类型通常用于类型扩展,确保一个类型同时具有多个类型的特性
交叉类型使用 & 符号来定义

语法:

T1 & T2 & ... & Tn

示例:

interface A {
  a: string;
}

interface B {
  b: number;
}

type C = A & B;

const c: C = {
  a: 'hello',
  b: 42,
};

2、联合类型

联合类型表示一个值可以是几种类型之一
联合类型通常用于类型保护,确保一个类型属于多个类型中的一个
联合类型使用 | 符号来定义

语法:

T1 | T2 | ... | Tn

示例:

type Options = 'A' | 'B' | 'C';

const option: Options = 'A';

3、类型保护

类型保护允许你在代码块中确保变量属于某个特定的类型

3.1 用户自定义的类型保护

定义:

interface Bird {
    fly(): void;
    layEggs(): void;
}
interface Fish {
    swim(): void;
    layEggs(): void;
}


// 定义一个类型保护函数 isFish
function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

使用:

// 'swim' 和 'fly' 调用都没有问题了
if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

3.2 typeof 类型保护

function getLength(arg: number| string): number {
    if (typeof arg === 'string') {
        return arg.length;
    } else {
        return arg.toString().length;
    }
}

3.3 instanceof类型保护

const curDate = new Date();
if (curDate instanceof Date) {
    console.log(curDate.getHours());
}

4、类型别名

4.1 定义

类型别名会给一个类型起个新名字。
类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型
起别名不会新建一个类型 - 它创建了一个新 名字来引用那个类型
关键字:type

示例:

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}
getName('ts')
getName(() => 'ts');
// getName(() => 123); // 报错

同接口一样,类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入:

type Container<T> = { value: T };

- 接口 vs. 类型别名

相同点:
1、都可以定义对象

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

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

2、都可以定义函数

interface addTypei {
  (num1: number, num2: number): number;
}

type addTypet = (num1: number, num2: number) => number;

不同点:
1、interface 使用 extends 实现继承, type 使用交叉类型实现继承
interface 继承 interface:

interface Person {
  name: string;
}
interface Student extends Person {
  grade: number;
}

type 继承 type:

type Person = {
  name: string;
}
type Student = Person & { grade: number }  // 用交叉类型

interface 继承 type:

type Person = {
  name: string;
}
interface Student extends Person {
  grade: number;
}

type 继承 interface:

interface Person {
  name: string;
}
type Student = Person & { grade: number }  // 用交叉类型

2、interface可以合并重复声明,type 不行(重复声明 type ,就报错了)

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

const person: Person = {
    name: 'ts',
    age: 12,
}

3、应用场景:
一般使用组合或者交叉类型的时候,用 type
一般要用类的 extendsimplements 时,用 interface

5、可辨识联合

你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做可辨识联合的高级模式,它也称做标签联合代数数据类型
分3步骤:

  1. 具有普通的单例类型属性— 可辨识的特征。
  2. 一个类型别名包含了那些类型的联合— 联合。
  3. 此属性上的类型保护。

示例:
步骤1:

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}

步骤2:

type Shape = Square | Rectangle | Circle;

步骤3:

function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
    }
}

完整性检查
当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。 比如,如果我们添加了 Triangle到 Shape,我们同时还需要更新 area:

type Shape = Square | Rectangle | Circle | Triangle;
function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
    }
    // should error here - we didn't handle case "triangle"
}

解决办法:
使用 never类型,编译器用它来进行完整性检查:

function assertNever(x: never): never {
    throw new Error("Unexpected object: " + x);
}
function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
        default: return assertNever(s); // error here if there are missing cases
    }
}

6、索引类型

使用索引类型,编译器就能够检查使用了动态属性名的代码

6.1 类型操作符:

先介绍两种新的类型操作符:

- 索引类型查询操作符:keyof T

keyof 操作符可以用于获取某种类型的所有键
其返回类型是联合类型
比如:

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

let personProps: keyof Person; // 'name' | 'age'

- 索引访问操作符:T[K]

T[K],表示接口 T 的属性 K 所代表的类型
示例:

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

let type1:  IPerson['name'] = 'hello' // string
let type2:  IPerson['age'] = 23 // number

console.log(type1) // hello
console.log(type2) // 23

6.2 使用索引类型

索引类型查询操作符 (keyof T) 和索引访问操作符 (T[K]), 你可以在泛型约束中使用它们来访问另一个类型的属性的类型
示例1:

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]; // T[K] 类型的返回值
}

let obj = {
  name: 'hello',
  age: 23,
}
getProperty(obj, 'name'); // 正确
getProperty(obj, 'grade'); // 错误,grade 不在 obj 中

示例2:
传入key的数组,获取对应的值的数组

function getValues<T, K extends keyof T>(userInfo: T, keys: K[]): T[K][] {
    return keys.map(key => userInfo[key]);
}

const userInfo = {
    name: 'ts',
    age: 12,
}
getValues(userInfo, ['name', 'age'])
// getValues(userInfo, ['sex', 'age']) // 这样当我们指定不在对象里的属性时,就会报错

7、映射类型

在 TypeScript 中是一种基于现有类型生成新类型的方式,它通过一个已知的类型,映射出一个新的类型
映射类型的基本形式使用一个 for…in 这样的循环语法,遍历某个类型的属性键,在映射过程中,你可以为这些属性应用修饰符或其他类型转换

in 操作符

用来对联合类型实现遍历

type Person = "name" | "school" | "major";
type obj = {
    [p in Person]: string;
}

7.1 Partial 可选

使用

PartialT的所有属性映射为可选的
示例:

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

type PersonPartial = Partial<Person>;

// 使用 Partial 改造一下,就可以变成可选属性
let P1: PersonPartial = {}; 

原理

type MyPartial<T> = {
  [P in keyof T]?: T[P];
} 

7.2 Readonly 只读

使用

ReadonlyT的所有属性映射为只读的
示例:

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

type PersonReadonly = Readonly<Person>;

let p1: PersonReadonly = {
  name: 'hello',
  age: 12
}; 
// p1.name = 'world' // 报错

原理

type MyReadonly<T> = {
    readonly [P in keyof T]: T[P];
}

7.3 Pick 抽取子集

Pick用于抽取对象子集,挑选一组属性并组成一个新的类型

使用

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

type PersonPick = Pick<Person, 'name' | 'age'>;

let p1: PersonPick = {
  name: 'hello',
  age: 12
}; 

原理

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

7.4 Record

用于创建一个对象类型,其属性键为 Keys,属性值类型为 Type
这使得我们能快速的构造一个具有固定类型值的对象,而键的部分则来自于一个联合类型或字面量类型

基本语法

Record<Keys, Type>
  • Keys 是一个字符串(或数字)字面量的联合类型,表示可枚举的键。
  • Type 是任何有效的 TypeScript 类型,表示每个属性的值的类型。

示例1:

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

type PersonRecord = Record<string, Person>

let personMap: PersonRecord = {
  p1: {
    name: 'hello',
    age: 12
  }  
}

示例2:

type PageInfo = {
  title: string;
};

// 字符串字面量的联合类型
type Page = 'home' | 'about' | 'contact';

// Record 类型将 Page 类型的每个属性都映射到 PageInfo 类型
const nav: Record<Page, PageInfo> = {
  home: { title: 'Home' },
  about: { title: 'About' },
  contact: { title: 'Contact' }
};

原理

type Record<K extends keyof any, T> = {
    [P in K]: T
}

7.5 有条件的映射类型

使用条件类型在映射时添加逻辑

T extends U ? X : Y 
// 若类型 T 可被赋值给类型 U,那么结果类型就是 X 类型,否则就是 Y 类型
// extends在这里不是用于类继承,而是表示一个条件类型查询,即“T 是否可以赋值给 U” 

预定义的有条件类型:

7.5.1 Exclude

Exclude – 从T中剔除可以赋值给U的类型
示例:

type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
let t1: T00 = 'b';
let t2: T00 = 'd';
// let t3: Test = 'a'; // 报错,不包含'a'

type T02 = Exclude<string | number | (() => void), Function>;  // string | number

Exclude 原理

  • never表示一个不存在的类型
  • never与其他类型的联合后,为其他类型
type Exclude<T, U> = T extends U ? never : T

示例:

type T1 = Exclude<'a' | 'b' | 'c', 'a'>;
// 类型为 'b' | 'c',因为 'a' 可以赋值给 'a',所以被排除了。

type T2 = Exclude<'a' | 'b' | 'c', 'a' | 'b'>;
// 类型为 'c',因为 'a' | 'b' 都可以赋值给 'a' | 'b',所以被排除了。

7.5.2 Extract

Extract – 提取T中可以赋值给U的类型

type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "a" | "c"

type T02 = Extract<string | number | (() => void), Function>;  // () => void

Extract 原理

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

7.5.3 NonNullable

NonNullable – 从T中剔除null和`undefined

type T04 = NonNullable<string | number | undefined>;  // string | number
type T05 = NonNullable<(() => string) | string[] | null | undefined>;  // (() => string) | string[]

7.5.3 ReturnType

ReturnType – 获取函数返回值类型

type T10 = ReturnType<() => string>;  // string
type T11 = ReturnType<(s: string) => void>;  // void
type T12 = ReturnType<(<T>() => T)>;  // {}
type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>;  // number[]

function f1(s: string) {
    return { a: 1, b: s };
}
type T14 = ReturnType<typeof f1>;  // { a: number, b: string }

type T15 = ReturnType<any>;  // any
type T16 = ReturnType<never>;  // any
type T17 = ReturnType<string>;  // Error
type T18 = ReturnType<Function>;  // Error

7.5.3 InstanceType

InstanceType – 获取构造函数类型的实例类型

class C {
    x = 0;
    y = 0;
}

type T20 = InstanceType<typeof C>;  // C
type T21 = InstanceType<any>;  // any
type T22 = InstanceType<never>;  // any
type T23 = InstanceType<string>;  // Error
type T24 = InstanceType<Function>;  // Error

你可能感兴趣的:(TypeScript,typescript,前端)