# TypeScript 高级类型

  1. class 类型
  2. 类型兼容性
  3. 交叉类型
  4. 泛型 和 keyof
  5. 索引签名类型 和 索引查询类型
  6. 映射类型

# 1. class 类型

说明:

  • TS 支持 class 关键字,并为其添加了类型注解和其他语法

示例:

class Person {
  age: number;
  gender = '男'
}

let p: Person;

p = new Person();

p.age;
p.gender;

# 1.1. 构造函数

说明:

  • 用于 成员初始化
  • 不需要返回值类型

示例:

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

const p = new Person('张三', 18);

console.log(p.name, p.age);

# 1.2. 实例方法

说明:

  • 方法的类型注解与函数的相同

示例:

class Point {
  x = 10;
  y = 20;

  scale(n: number): void {
    this.x *= n;
    this.y *= n;
  }
}

const p = new Point();

p.scale(10);

console.log(p.x, p.y);

# 1.3. extends(继承)

说明:

  • JS 中也有 extends
  • 子类复用父类的属性和方法

示例:

class Animal {
  move() {
    console.log('move');
  }
}

class Dog extends Animal {
  name = '二哈';
  bark() {
    console.log('汪汪');
  }
}

const dog = new Dog();
dog.move();
dog.bark();
console.log(dog.name);

# 1.4. implements(实现接口)

说明:

  • 类 必须实现 接口 中所有的属性和方法

示例:

interface Singable {
  sing(): void;
}

class Person implements Singable {
  sing() {
    console.log('你是我的小苹果!');
  }
}

# 1.5. 可见性修饰符

说明:

  • 用于控制在 类外 是否可以访问类的属性和方法
  • 可见性修饰符:
    • public: 公开的,默认的。当前类及实例、子类及实例 都可以访问。
    • protected: 受保护的。当前类、子类中可以访问到。(非实例对象)
    • private: 私有的。当前类才可以访问

示例:

class Animal {
  private __name__: string;

  private __run__() {
    console.log('run');
  }
  
  protected move(): void {
    console.log('move');
  }

  public setName(name: string) {
    this.__name__ = name;
  }
  public getName(): string {
    return this.__name__;
  }
}

class Dog extends Animal {
  run() {
    this.move();
  }
}

const animal = new Animal();
const dog = new Dog();

// animal.move(); // 访问不了

console.log( animal.getName() );

# 1.6. readonly(只读修饰符)

说明:

  • readonly,表示只读,修饰属性
  • 类的成员属性 上使用,表示只能在构造函数中赋值
  • 接口或对象类型注解 中使用,表示属性是只读的

示例:

class Person {
  readonly name: string = '';

  // 如果不明确设置类型注解,则推论为字面量类型
  readonly age = 18;

  constructor(name: string, age: number) {
    this.name = name;
    // this.age = age;
  }
}

interface IPerson {
  readonly name: string;
}

let p1: IPerson = { name: '张三' };
// 不能修改
// p1.name = ''

let p2: { readonly name: string } = { name: '李四' };
// p2.name = ''

# 2. 类型兼容性

# 2.1. 介绍

说明:

  • 有两种类型系统:结构化类型系统,标明类型系统
  • 结构化类型系统,也叫鸭子类型,只关注值所具有的形状
  • 如果两个对象具有相同的形状,则认为它们是同一类型
  • TS 采用结构化类型系统

示例:

class Male { name: string }
class Female { name: string }

// 发生的类型兼容
let p1: Male = new Female();

# 2.2. 类之间的类型兼容

说明:

  • 成员多的可以赋值给成员少的

示例:

class Point2D { x: number; y: number }
class Point3D { x: number; y: number; z: number }

let p: Point2D = new Point3D();

# 2.3. 接口之间的类型兼容

说明:

  • 接口之间的兼容性,类似与 class,多的可以赋值给少的

示例:

interface IPoint   { x: number, y: number }
interface IPoint2D { x: number, y: number }
interface IPoint3D { x: number, y: number, z: number }

let p1: IPoint;
let p2: IPoint2D;
let p3: IPoint3D;

// 相同属性
p1 = p2;

// 多的赋值给少的
p1 = p3;

# 2.4. 接口与类之间的类型兼容

说明:

  • 接口 与 类 之间的兼容性,类似于 class

示例:

interface IPoint2D { x: number, y: number }
class Point3D      { x: number; y: number; z: number }

let p2: IPoint2D;

p2 = new Point3D();

# 2.5. 函数之间的类型兼容

函数之间兼容性比较复杂,需要考虑:

  1. 参数个数
  2. 参数类型
  3. 返回值类型

# 2.5.1. 参数个数

说明:

  • JS 中省略用不到的参数非常常见
  • TS 中可以将 参数少的 赋值给 参数多的

示例:

type F1 = (a: number) => void;
type F2 = (a: number, b: number) => void;

let f1: F1 = (a) => a;
let f2: F2;

f2 = f1;


/*
  forEach 需要的回调函数的参数类型为:
    (value: number, index: number, array: number[]) => void      
 */
let arr = [1, 2, 3];
arr.forEach(() => {});
arr.forEach((item) => {});
arr.forEach((item, index) => {});
arr.forEach((item, index, array) => {});

# 2.5.2. 参数类型

说明:

  • 相同位置的参数的类型,分两种情况:基本类型、对象类型(类、接口、对象)
  • 基本类型:要相同
  • 对象类型:摊开后,参数少的 可赋值给 参数多的

示例1:

// 基本类型
type F1 = (a: number) => void;
type F2 = (b: number) => void;

let f1: F1 = (x) => x;
let f2: F2;

f2 = f1;

示例2:

// 对象类型
type F1 = (p1: { x: number }) => void;
type F2 = (p2: { x: number, y: number }) => void;

let f1: F1 = (p) => {};
let f2: F2;

f2 = f1;
/*
Type 'F2' is not assignable to type 'F1'.
  Types of parameters 'p2' and 'p1' are incompatible.
*/
f1 = f2;

# 2.5.3. 返回值类型

说明:

  • 返回值类型为 基本类型,两类型要相同
  • 返回值类型为 对象类型,多的可以赋值给少的 (对象兼容性)

示例:

type F1 = () => string;
type F2 = () => string;

let f1: F1;
let f2: F2;

// f1 = f2;
f2 = f1;


type F3 = () => { a: number };
type F4 = () => { a: number, b: string };

let f3: F3;
let f4: F4;

f3 = f4;
// f4 = f3; // 不行

# 3. 交叉类型

说明:

  • 功能类似于接口继承,用于组合多个类型为一个类型
  • 常用于对象类型
  • 使用 & 连接多个类型

示例:

interface Person { name: string }
interface Contact { phone: string }

type PersonDetail = Person & Contact;

let p: PersonDetail = {
  name: '张三',
  phone: '123'
}

# 3.1. 交叉类型 vs 接口继承

  • 相同点:都可以实现对象类型的组合
  • 不同点:处理同名属性的方式不一样
interface A { x: string }

/*
TS2430: Interface 'B' incorrectly extends interface 'A'.
  Types of property 'x' are incompatible.
    Type 'number' is not assignable to type 'string'.
 */
// interface B extends A { x: number }

interface C { x: string }
interface D { x: number }

type E = C & D;

// (property) x: never
let e: E = { x: 1 };

# 4. 泛型

# 4.1. 泛型函数

# 4.1.1. 基本使用

说明:

  • 泛型,在保证类型安全前提下,让函数支持多种类型。
  • 常用于 函数、接口、class

泛型函数:

/*
  定义泛型函数:
  
    函数名称后面的 `<Type>`:
      Type 为类型变量的名称,可以在类型注解中使用
 */
function id<Type>(value: Type): Type {
  return value;
}

/*
  调用泛型函数:
  
    调用函数时,给类型变量赋值
 */
let num: number = id<number>(123);
let str: string = id<string>('a');

# 4.1.2. 简化调用泛型函数

说明:

  • 调用泛型函数时,可以省略 <Type>,交给 TS 的类型参数推断机制来做
  • 如果类型推断不准确,再显式给类型变量赋值

示例:

function id<Type>(value: Type): Type {
  return value;
}

// 等价于 let num = id<number>(123);
let num = id(123);

# 4.2. 泛型约束

说明:

  • 类型变量可以是任意的类型,也就是说它无法使用类型独有的属性和方法
  • 限制泛型的类型变量的值的范围,有两种方式:
    1. 指定更加具体的类型
    2. 添加约束

# 4.2.1. 指定更加具体的类型

比如,将类型修改为 Type[]:

function id<Type>(value: Type[]): Type[] {
  console.log(value.length);
  return value;
}

# 4.2.2. 添加约束

说明:

  • 通过关键字 extends 给泛型的类型变量添加约束

示例:

interface ILength { length: number }

// 类型变量 Type 要满足 ILength 的要求,即要求有 length 属性
function id<Type extends ILength>(value: Type): Type {
  console.log(value.length);
  return value;
}

id([1, 2, 3]);
id({ length: 2, '0': 1, '1': 2 });

# 4.3. 多个类型变量

说明:

  • 泛型的类型变量可以有多个,并且类型变量之间可以约束

示例:

/*
  keyof 对象
    生成对象键名称的联合类型
    如 typeof { name: '1', age: 2 } => 'name' | 'age'
 */
function getProp<Type, Key extends (keyof Type)>(obj: Type, key: Key) {
  return obj[key];
}

let stuName: string = getProp({ name: '张三', age: 18 }, 'name');
let age: number = getProp({ name: '张三', age: 18 }, 'age');

# 4.4. 泛型接口

# 4.4.1. 基本使用

说明:

  • 在接口名称后面添加 <类型变量>,这个接口就变成泛型接口了
  • 接口中所有成员都可以使用该类型变量
  • 使用泛型接口时,要显式指定类型变量的值

示例:

interface IStudent<Type> {
  id: Type;
  getId(): Type;
  setId(id: Type): Type;
}

let stu: IStudent<string> = {
  id: '123',
  
  getId(): string {
    return this.id;
  },
  
  setId(id: string): string {
    this.id = id;
    return this.id;
  }
}

# 4.4.2. 数组泛型接口

说明:

  • 数组在 TS 中就是一个泛型接口
  • 在官方 lib.es5.d.ts 文件中有数组的接口类型

示例:

// lib.es5.d.ts

interface Array<T> {
  /**
   * Performs the specified action for each element in an array.
   * @param callbackfn  A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
   * @param thisArg  An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
   */
  forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
  
  // ...
}

# 4.5. 泛型类

说明:

  • 定义时,在类名称后面声明 <类型变量>
  • 调用时,在类名称后面给 <类型变量> 赋值

示例:

class GenericNumber<T> {
  defaultValue: T;

  constructor(value: T) {
    this.defaultValue = value;
  }

  add(num: T): T {
    return this.defaultValue;
  }
}

// 显式设置类型变量的值
let n1 = new GenericNumber<number>(10);

// 类型推论
let n2 = new GenericNumber(10);

# 4.6. 泛型工具类型

# 4.6.1. 介绍

说明:

  • TS 内置了一些常用的工具类型
  • 它们都是基于泛型实现的,可以直接在代码中使用

工具类型:

  1. Partial<Type>
  2. Readonly<Type>
  3. Pick<Type, Keys>
  4. Record<Keys, Type>

# 4.6.2. Partial

说明:

  • Partial<Type>
  • 创建一个结构与 Type 完全相同的类型,但所有属性都是可选的

示例:

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

/*
  { name?: string, age?: number }
 */
type PartialPerson = Partial<IPerson>;

# 4.6.3. Readonly

说明:

  • Readonly<Type>
  • 创建一个结构与 Type 完全相同的类型,但所有属性都是只读的

示例:

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

/*
  { readonly name: string, readonly age: number }
 */
type ReadonlyPerson = Readonly<IPerson>;

# 4.6.4. Pick

说明:

  • Pick<Type, Keys>
  • Type 中选择 Keys 指定的属性来创建新类型

示例:

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

/*
  { name: string, age: number }
 */
type PickPerson = Pick<IPerson, 'name' | 'age'>;

# 4.6.5. Record

说明:

  • Record<Keys, Type>
  • 创建一个对象类型,Keys 指定有哪些属性,Type 指定属性的类型
  • 所有属性的类型都为 Type

示例:

/*
  { name: string, gender: string }
 */
type RecordObj = Record<'name' | 'gender', string>;

# 5. 索引签名类型

# 5.1. 基本使用

说明:

  • 绝大多数情况下,在使用对象前就确定对象的结构,并未对象添加准确的类型
  • 当无法确定对象有哪些属性(或对象中可以出现任意的属性)时,可以使用索引签名类型

示例:

interface AnyObject {
  /*
    key 为占位符,可以为任意的标识符
    对象中的属性名称都为字符串
   */
  [key: string]: number
}

let obj: AnyObject = {
  a: 1,
  b: 2,
}

# 5.2. 数组的键

说明:

  • 数组是特殊的对象,键(索引)是数值类型
  • 数组对应的泛型接口中,也用到了索引签名类型

示例:

// lib.es5.d.ts
interface Array<T> {
  // ...
  
  // 数组的 index,及对应的值类型
  [n: number]: T;
}


// 模拟数组
interface MyArray<T> {
  [index: number]: T;
}

let arr: MyArray<number> = [1, 2]
console.log( arr[0] );

# 6. 映射类型

# 6.1. 基本使用

说明:

  • 基于已有类型创建新类型(对象类型)
  • 映射类型是基于索引签名类型的,语法类似
  • 映射类型只能在类型别名中使用,不能在接口中使用

示例:

type PropsKeys = 'x' | 'y' | 'z';

/*
  { x: number, y: number, z: number }
 */
type Type = {
  [Key in PropsKeys]: number;
}

# 6.2. keyof

说明:

  • keyof 对象类型: 获取对象类型中所有键的联合类型
  • keyof { x: number, y: number }: "x" | "y"

示例:

type Props = { x: number, y: number, z: number }

/*
  配合映射类型使用:
  
  { x: number, y: number, z: number }
 */
type Type = {
  [Key in (keyof Props)]: number;
};

# 6.3. Partial 的实现

lib.es5.d.ts:

/**
 * Make all properties in T optional
 */
type Partial<T> = {
  /*
    keyof T
      表示获取 T 中所有键的联合类型
    
    ?
      表示可选的
      
    T[P]
      表示获取 T 中对应键 P 的类型
   */
  [P in keyof T]?: T[P];
};

# 6.4. 索引查询类型

说明:

  • T[P] 语法,叫做 索引查询类型(或索引访问类型)
  • 作用:用来查询属性的类型

示例:

type Props = {
  x: number,
  y: string,
  z: boolean,
  i: number,
}

// 获取单个属性的类型
type Type1 = Props['x']; // number

// 获取多个属性类型
type Type2 = Props['x' | 'y']; // number | string

// 获取所有属性的类型
type Type3 = Props[keyof Props]; // number | string | boolean
本章目录