一文搞懂 TypeScript 类型系统:从基础类型到 Class 实战

164 阅读3分钟

1.常见的类型

number(数字类型)

let age: number = 25
let price: number = 99.99
let hex: number = 0xff // 十六进制
let binary: number = 0b1010 // 二进制
let octal: number = 0o744 // 八进制

string(字符串类型)

let firstName: string = 'Alice'
let greeting: string = `Hello, ${firstName}!` // 模板字符串

boolean(布尔类型)

let isDone: boolean = true
let hasPermission: boolean = false

null 和 undefined(空值和未定义类型)

let u: undefined = undefined
let n: null = null

默认情况下,null 和 undefined 只能赋值给 null 或 undefined,但如果 strictNullChecks 关闭,它们也可以赋值给其他类型。

bigint(大整数)

let bigNum: bigint = 12345678901234567890n

symbol(唯一值)

const sym1: symbol = Symbol('unique')
const sym2: symbol = Symbol('unique')
console.log(sym1 === sym2) // false

数组类型

数组类型用来表示元素类型一致的数组。可以通过两种方式定义数组类型:

const list: number[] = [1, 2, 3]
const lsit2: Array<number> = [1, 2, 3] // 泛型数组

元组类型

元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同。

let x: [string, number] = ['hello', 10]
x = ['hello', 10] // 合法
x = [10, 'hello'] // 不合法
// 当访问一个已知索引的元素,会得到正确的类型:
console.log(x[0].substring(1))
// 当访问一个越界的元素,会使用联合类型替代:
console.log(x[1].substring(1))

枚举类型

枚举类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

enum Color {
  Red,
  Green,
  Blue,
}

let c: Color = Color.Green

任意类型

任意类型(Any)用来表示允许赋值为任意类型。

let notSure: any = 4
notSure = 'maybe a string instead'
notSure = false

空值

空值(Void)用来表示没有任何类型。

function warnUser(): void {
  console.log('This is my warning message')
}

Unknown 类型

unknown 类型表示未知类型的值,类似于 any,但更安全,因为对 unknown 类型的值进行操作之前需要进行类型检查。

let v: unknown = 12
v = 'hello'
v = true
if (typeof v === 'string') {
  console.log(v.toUpperCase())
}

Never 类型

Never 类型表示那些永不存在的值的类型,例如抛出错误或无限循环。

function error(message: string): never {
  throw new Error(message)
}

函数类型

函数类型用来表示函数的类型,包括参数类型和返回值类型。

function add(x: number, y: number): number {
  return x + y
}

交叉类型

交叉类型用来组合多个类型成一个类型,表示对象可以同时具有多个类型的成员。

interface Person {
  name: string
  age: number
}
interface Employee {
  salary: number
}
type EmployeePerson = Person & Employee
const ep: EmployeePerson = {
  name: 'John',
  age: 30,
  salary: 50000,
}

索引类型

索引类型允许使用动态属性名来访问类型中的属性。

interface Person {
  name: string
  age: number
}
type PersonKeys = keyof Person // "name" | "age"
type NameType = Person['name'] // string

条件类型

条件类型是一种基于类型的条件判断语法,可以根据条件返回不同的类型。语法如下:

ttype Check<T> = T extends string ? string : number
type Result1 = Check<string> // string
type Result2 = Check<number> // number

联合类型

联合类型允许一个变量可以是几种类型之一。用竖线 | 分隔不同的类型。

type MyUnion = string | number | boolean
let value: MyUnion
value = 'Hello' // 合法
value = 123 // 合法
value = true // 合法
value = {} // 不合法

递归类型

递归类型是指类型引用自身的一部分,用于定义复杂的嵌套结构,如树结构、链表等。

interface TreeNode {
  value: string
  children?: TreeNode[]
}

const tree: TreeNode = {
  value: 'root',
  children: [
    { value: 'child1' },
    {
      value: 'child2',
      children: [{ value: 'grandchild1' }],
    },
  ],
}

映射类型

映射类型允许根据已有类型创建新类型,通过遍历已有类型的属性来生成新类型。

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

type User = {
  name: string
  age: number
}

type ReadonlyPerson = MyReadonly<User>

const User: ReadonlyPerson = {
  name: 'Alice',
  age: 30,
}

User.name = 'Bob' // 报错,name 是只读属性

2.类型断言

解决的问题:保证和检测来自其他地方的数据也符合我们的要求

类型断言的两种方式

尖括号语法

let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
console.log(strLength);

as语法

let someValue2: any = 'this is a string';
let strLength2: number = (someValue2 as string).length;
console.log(strLength2);

非空断言

解决的问题:当我们确定一个变量不为空时,我们可以使用非空断言来告诉TypeScript编译器,该变量不为空

忽略 undefined 和 null 类型

function myFunc(maybeString: string | undefined | null) {
  const onlyString: string = maybeString; // error:不能将类型“string | null | undefined”分配给类型“string”
  const ignoreUndefinedAndNull: string = maybeString!; // Ok
}

调用函数时忽略 undefined 类型

type NumGenerator = () => number;

function myFunc2(numGenerator: NumGenerator | undefined) {
  const num1 = numGenerator(); // error:numGenerator”可能为“未定义。
  const num2 = numGenerator!(); //OK
}

确定赋值断言

let x: number;
initialize();
console.log(2 * x); // Error:在赋值前使用了变量“x”
function initialize() {
  x = 10;
}

很明显该异常信息是说变量 x 在赋值前被使用了,要解决该问题,我们可以使用确定赋值断言

let x!: number;
initialize();
console.log(2 * x); // Ok
function initialize() {
  x = 10;
}

双重断言

解决的问题:当我们需要将一个联合类型断言为更加具体的类型时,我们可以使用双重断言

const str: string = 'aiolimp';
// 从 X 类型 到 Y 类型的断言可能是错误的
(str as { handler: () => {} }).handler(); // error
const str: string = 'aiolimp';
(str as unknown as { handler: () => {} }).handler();
// 使用尖括号断言
(<{ handler: () => {} }>(<unknown>str)).handler();

3.类型守卫

类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:

in 关键字

in 用于检查 对象是否具有某个属性。

type Car = { brand: string; drive: () => void };
type Boat = { brand: string; sail: () => void };

function operate(vehicle: Car | Boat) {
  if ('drive' in vehicle) {
    vehicle.drive();
  } else {
    vehicle.sail();
  }
}

const myCar: Car = { brand: 'Tesla', drive: () => console.log('Driving...') };
const myBoat: Boat = { brand: 'Yacht', sail: () => console.log('Sailing...') };

operate(myCar); // Driving...
operate(myBoat); // Sailing...

typeof 关键字

typeof 适用于检查 number、string、boolean、symbol、bigint、object、function 等基本类型。

const str = 'aiolimp';
const obj = { name: 'aiolimp' };
const nullVar = null;
const undefinedVar = undefined;

const func = (input: string) => {
  return input.length > 10;
};

type Str = typeof str; // "aiolimp"
type Obj = typeof obj; // { name: string; }
type Null = typeof nullVar; // null
type Undefined = typeof undefined; // undefined
type Func = typeof func; // (input: string) => boolean

不仅可以直接在类型标注中使用 typeof,还能在工具类型中使用 typeof。

const func2: typeof func = (name: string) => {
  return name === 'linbudu';
};

instanceof 关键字

instanceof 用于检查一个对象是否是另一个对象的实例。

class Animal {
  makeSound() {
    console.log('Some sound...');
  }
}

class Dog extends Animal {
  bark() {
    console.log('Woof!');
  }
}

function makeNoise(animal: Animal) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.makeSound();
  }
}

const dog = new Dog();
makeNoise(dog); // Woof!

自定义类型谓词(is 关键字)

自定义类型守卫使用 返回 value is Type 形式的函数,明确告诉 TypeScript 变量的具体类型。

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function isFish(animal: Fish | Bird): animal is Fish {
  return (animal as Fish).swim !== undefined;
}

function move(animal: Fish | Bird) {
  if (isFish(animal)) {
    animal.swim();
  } else {
    animal.fly();
  }
}

const fish: Fish = { swim: () => console.log('Swimming...') };
const bird: Bird = { fly: () => console.log('Flying...') };

move(fish); // Swimming...
move(bird); // Flying...

4.class类

定义变量

在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明

class Person1 {
  name: string;
  age: number = 20; // 设置默认值
  // 在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayHello() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

类的修饰符

class Person2 {
  public name: string;
  private age: number;
  protected gender: string;
  constructor(name: string, age: number, gender: string) {
    this.name = name;
    this.age = age;
    this.gender = gender;
  }
  sayHello() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

class Student2 extends Person2 {
  constructor() {
    super('张三', 20, '男');
    console.log(this.name); // 可以访问
    // console.log(this.age); // 报错
    console.log(this.gender); // 可以访问
  }
}
let person2 = new Person2('John', 30, '男');
person2.name = 'Jane'; // 使用public 修饰符 可以让你定义的变量 内部访问 也可以外部访问 如果不写默认就是public
// person2.age = 31; //报错 使用private 修饰符 只能在类的内部访问 外部不能访问
// person2.gender = '女'; //报错 使用protected 修饰符 只能在类的内部和子类中访问 外部不能访问

let student2 = new Student2();
student2.name = '李四';
// student2.age = 31; //报错
// student2.gender = '女'; //报错
  • 使用public 修饰符 可以让你定义的变量 内部访问 也可以外部访问 如果不写默认就是public
  • 使用private 修饰符 只能在类的内部访问 外部不能访问
  • 使用protected 修饰符 只能在类的内部和子类中访问 外部不能访问

静态属性和静态方法

static 定义的属性 不可以通过this 去访问 只能通过类名去调用

class Person3 {
  static name: string;
  constructor(name: string) {
    // this.name = name; //报错 用static 定义的属性 不可以通过this
  }
}
Person3.name = '王五'; // 访问static 定义的属性 可以通过类名访问

static 静态函数 同样也是不能通过this 去调用 也是通过类名去调用

class Person3 {
  constructor(name: string) {
    // this.run(); // 报错 用static 定义的方法 不可以通过this
  }

  static run() {
    console.log('run');
  }
}
Person3.run(); // 访问static 定义的方法 可以通过类名访问

需注意: 如果两个函数都是static 静态的是可以通过this互相调用

class Person3 {
  constructor(name: string) {
    // this.name = name; //报错 用static 定义的属性 不可以通过this
    // this.run(); // 报错 用static 定义的方法 不可以通过this
  }

  static run() {
    console.log('run');
    this.say(); // 需注意: 如果两个函数都是static 静态的是可以通过this互相调用
  }

  static say() {
    this.run(); // 需注意: 如果两个函数都是static 静态的是可以通过this互相调用
  }
}

完整代码

class Person3 {
  static name: string;
  constructor(name: string) {
    // this.name = name; //报错 用static 定义的属性 不可以通过this
    // this.run(); // 报错 用static 定义的方法 不可以通过this
  }

  static run() {
    console.log('run');
    this.say(); // 需注意: 如果两个函数都是static 静态的是可以通过this互相调用
  }

  static say() {
    this.run(); // 需注意: 如果两个函数都是static 静态的是可以通过this互相调用
  }
}
Person3.name = '王五'; // 访问static 定义的属性 可以通过类名访问
Person3.run(); // 访问static 定义的方法 可以通过类名访问

interface定义类

interface 定义类 使用关键字 implements 后面跟interface的名字多个用逗号隔开 继承还是用extends

interface PersonClass {
  get(type: boolean): boolean;
}

interface PersonClass2 {
  set(): void;
  address: string;
}

class A {
  name: string;
  constructor() {
    this.name = '222';
  }
}
// interface 定义类 使用关键字 implements   后面跟interface的名字多个用逗号隔开 继承还是用extends
class Person extends A implements PersonClass, PersonClass2 {
  address: string;
  constructor() {
    super();
    this.address = '333';
  }
  get(type: boolean) {
    return type;
  }
  set() {}
}

抽象类

通过abstract关键字定义抽象类和抽象方法

解决的问题:当我们定义一个类时,我们可以使用抽象类来定义这个类的结构,子类必须实现抽象类中的抽象方法。

应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类,或者你也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法

象类无法被实例化

abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  print(): string {
    return this.name;
  }

  abstract say(): string;
}

// new Animal(); // 报错 无法创建抽象类的实例

子类必须实现抽象类中的抽象方法

abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  print(): string {
    return this.name;
  }
  abstract say(): string;
}

class Dog extends Animal {
  constructor() {
    super('wangwangwang');
  }
  // 子类必须实现抽象类中的抽象方法
  say(): string {
    return this.name;
  }
}
let dog = new Dog();