深入浅出 TypeScript | 青训营

297 阅读9分钟

为什么要学习TypeScript

JavaScript(JS)和TypeScript(TS)都是用于构建前端和后端应用程序的编程语言,但它们在一些方面有所不同。

  1. 类型系统:

    • JavaScript: JavaScript是一种动态类型语言,变量的类型是在运行时确定的。这意味着无需在声明变量时指定其类型。
    • TypeScript: TypeScript是JavaScript的超集,它引入了静态类型系统。在TypeScript中,可以在编码阶段指定变量的类型,从而提供更早的错误检查和代码提示。
  2. 类型注解:

    • JavaScript: JavaScript没有原生的类型注解,变量类型通常由赋值操作决定。
    • TypeScript: TypeScript支持类型注解,可以使用类型来明确变量的类型。
  3. 编译:

    • JavaScript: JavaScript代码在运行之前不需要编译,可以直接在浏览器或Node.js环境中执行。
    • TypeScript: TypeScript代码需要被编译成JavaScript代码,以便在浏览器或Node.js环境中运行。TypeScript的编译过程将类型注解转换为普通的JavaScript代码。
  4. 代码提示和自动补全:

    • JavaScript: JavaScript的代码提示和自动补全有限,通常依赖于编辑器的基本功能。
    • TypeScript: TypeScript具有强大的代码提示和自动补全功能,因为它在编译时了解变量的类型。
  5. 类型安全性:

    • JavaScript: 由于动态类型的特性,JavaScript在运行时可能会出现类型相关的错误。
    • TypeScript: TypeScript的静态类型系统可以在编码阶段捕获类型错误,从而减少运行时错误的风险。
  6. 生态系统:

    • JavaScript: JavaScript是一种广泛使用的编程语言,具有大量的开源库和框架。
    • TypeScript: TypeScript在现代Web开发中越来越受欢迎,许多流行的框架和库都提供了TypeScript的支持。
  7. 学习曲线:

    • JavaScript: JavaScript是一种相对容易学习的语言,适合初学者入门。
    • TypeScript: TypeScript相对于JavaScript来说需要更多的学习,特别是对于静态类型和类型注解的概念。
  8. 适用场景:

    • JavaScript: 适用于快速原型开发、小型项目以及前端开发。
    • TypeScript: 适用于大型项目、团队协作以及需要更强的类型安全性的场景。

TypeScript基础

基础类型

  1. number: 用于表示数值类型,包括整数和浮点数。

    示例:

    let age: number = 25;
    let price: number = 59.99;
    
  2. string: 用于表示字符串类型,可以包含文本和字符。

    示例:

    let name: string = "John";
    let message: string = "Hello, TypeScript!";
    
  3. boolean: 用于表示布尔类型,可以取truefalse

    示例:

    let isActive: boolean = true;
    let isStudent: boolean = false;
    
  4. array: 用于表示数组,可以包含相同类型的多个元素。

    示例:

    let numbers: number[] = [1, 2, 3, 4, 5];
    let fruits: string[] = ["apple", "banana", "orange"];
    
  5. tuple: 用于表示固定长度和类型的数组。

    示例:

    let person: [string, number] = ["John", 30];
    
  6. enum: 用于创建一组命名的常量值。

    示例:

    enum Color {
      Red,
      Green,
      Blue,
    }
    let selectedColor: Color = Color.Green;
    
  7. any: 用于表示任意类型,不进行类型检查。

    示例:

    let dynamicValue: any = "Hello, TypeScript!";
    dynamicValue = 42;
    
  8. void: 用于表示没有返回值的函数。

    示例:

    function logMessage(message: string): void {
      console.log(message);
    }
    
  9. null 和 undefined: 用于表示空值或未定义的值。

    示例:

    let emptyValue: null = null;
    let notDefined: undefined = undefined;
    
  10. never: 用于表示永远不会返回结果的函数类型,或者表示抛出异常或进入无限循环的情况。

    示例:

    function throwError(message: string): never {
      throw new Error(message);
    }
    
  11. object: 用于表示非原始类型(即除number、string、boolean、symbol、null、undefined之外的类型)。

    示例:

    let person: object = { name: "John", age: 25 };
    
  12. unknown: 类似于any,但更安全。需要进行类型检查或类型断言。

    示例:

    let userInput: unknown = "Hello, TypeScript!";
    if (typeof userInput === "string") {
      let upperCaseValue: string = userInput.toUpperCase();
    }
    

函数类型

TS定义函数类型时要定义输入参数和输出类型。以下为函数的使用方法:

  1. 函数声明:

    示例:

    function add(x: number, y: number): number {
      return x + y;
    }
    
  2. 函数表达式:

    示例:

    const subtract = function(x: number, y: number): number {
      return x - y;
    };
    
  3. 箭头函数:

    示例:

    const multiply = (x: number, y: number): number => x * y;
    
  4. 函数类型注解:

    示例:

    let calculate: (x: number, y: number) => number;
    calculate = add; // 可以赋值为具有相同签名的函数
    
  5. 可选参数和默认参数:

    示例:

    function greet(name: string, greeting?: string): string {
      if (greeting) {
        return `${greeting}, ${name}!`;
      } else {
        return `Hello, ${name}!`;
      }
    }
    
  6. 剩余参数:

    示例:

    function sumAll(...numbers: number[]): number {
      return numbers.reduce((total, num) => total + num, 0);
    }
    
  7. 重载: 名称相同但参数不同,可以通过重载支持多种类型。

    示例:

    function processValue(input: string): string;
    function processValue(input: number): number;
    function processValue(input: any): any {
      if (typeof input === 'string') {
        return input.toUpperCase();
      } else if (typeof input === 'number') {
        return input * 2;
      }
      return input;
    }
    
  8. 回调函数:

    示例:

    function fetchData(url: string, callback: (data: any) => void): void {
      // 调用网络请求,获取数据
      const data = ...; // 假设获得了数据
      callback(data); // 调用回调函数处理数据
    }
    
    fetchData('https://example.com/api', (response) => {
      console.log(response);
    });
    
  9. 函数类型接口:

    示例:

    interface BinaryOperation {
      (x: number, y: number): number;
    }
    
    const divide: BinaryOperation = (x, y) => x / y;
    

类的写法

在TypeScript中,类的写法与JavaScript类似,但它增加了类型注解和类型检查,使得类的使用更加类型安全。以下是TypeScript中类的写法示例,并与JavaScript进行了比较:

TypeScript类的写法和示例:

class Person {
  private name: string;
  private age: number;

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

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const john = new Person("John", 30);
john.greet();

在上述示例中,Person类具有私有属性nameage,并且包含构造函数和方法greet

JavaScript中类的写法和示例:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const john = new Person("John", 30);
john.greet();

在JavaScript中,类的写法与TypeScript类似,但没有类型注解和类型检查的支持。属性和方法可以随意访问,没有访问修饰符的概念。

TypeScript与JavaScript类的不同点:

  1. 类型注解: 在TypeScript中,可以为属性和方法添加类型注解,从而明确指定它们的数据类型。
  2. 访问修饰符: TypeScript引入了访问修饰符(privateprotectedpublic)来限制属性和方法的访问范围,增加了封装性。
  3. 类型安全性: TypeScript的类在编译时进行类型检查,可以捕获许多常见的错误,提高代码的稳定性。
  4. 构造函数参数属性: TypeScript提供了简化构造函数参数赋值的语法,可以在构造函数参数中直接声明属性,从而减少重复代码。
  5. 继承和实现接口: TypeScript的类支持类之间的继承,以及实现接口来强制定义特定的属性和方法。
  6. 静态属性和方法: TypeScript引入了静态属性和方法的概念,这些属性和方法属于类本身而不是实例。

下面是 TypeScript 中各种类型的类的写法示例:

  1. 普通类:

    普通类用于创建对象,可以包含属性和方法。

class Person {
  name: string;
  age: number;

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

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const john = new Person("John", 30);
john.greet();
  1. 继承类:

    继承类可以继承另一个类的属性和方法,并可以添加自己的属性和方法。

class Student extends Person {
  studentId: number;

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

  study() {
    console.log(`${this.name} is studying.`);
  }
}

const alice = new Student("Alice", 25, 12345);
alice.greet();
alice.study();
  1. 抽象类:

    抽象类用于定义基础结构,可以包含抽象方法和具体方法,不能被实例化,只能被继承。

abstract class Shape {
  abstract getArea(): number;

  displayArea() {
    const area = this.getArea();
    console.log(`Area: ${area}`);
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }

  getArea() {
    return Math.PI * this.radius * this.radius;
  }
}

const circle = new Circle(5);
circle.displayArea();
  1. 接口继承类:

    可以使用接口来扩展类的类型,使对象符合特定的接口。

class Car {
  constructor(public brand: string) {}
}

interface ElectricCar extends Car {
  batteryCapacity: number;
}

const tesla: ElectricCar = {
  brand: "Tesla",
  batteryCapacity: 85,
};
  1. 构造函数签名:

    使用构造函数签名可以定义一个构造函数类型,用于描述类的实例化过程。

interface Point {
  x: number;
  y: number;
}

class PointClass implements Point {
  constructor(public x: number, public y: number) {}
}

const point: Point = new PointClass(3, 4);

这些示例涵盖了 TypeScript 中各种类型的类的写法,从普通类到继承类和抽象类,以及如何使用接口来扩展类的类型。这些特性使得 TypeScript 的类能够更加灵活和类型安全。

TypeScript进阶

高级类型

  1. 泛型(Generics):泛型允许编写可以适用于多种类型的可重用代码。通过使用泛型,可以在编译时指定要操作的类型。泛型在 TypeScript 中的应用场景很多,以下是一些常见的应用场景和示例:

    • 函数参数的灵活性:当函数需要处理不同类型的参数时,可以使用泛型来增加函数的灵活性和复用性。例如,一个通用的 identity 函数可以返回传入的参数,而不改变其类型:
    function identity<T>(arg: T): T {
      return arg;
    }
    
    let result1 = identity<string>("Hello"); // result1 的类型为 string
    let result2 = identity<number>(42); // result2 的类型为 number
    
    • 数据结构的扩展性:当需要创建可扩展的数据结构时,泛型非常有用。例如,使用泛型来定义一个可存储不同类型数据的数组:
    let array: Array<number> = [1, 2, 3]; // 数组中的元素类型为 number
    
    • 类的复用性和类型安全:在类定义中使用泛型可以提高代码的复用性和类型安全性。例如,一个通用的 Pair 类可以存储一对值,并保持它们的类型一致:
    class Pair<T, U> {
      private first: T;
      private second: U;
    
      constructor(first: T, second: U) {
        this.first = first;
        this.second = second;
      }
    
      getFirst(): T {
        return this.first;
      }
    
      getSecond(): U {
        return this.second;
      }
    }
    
    let pair = new Pair<string, number>("Hello", 42);
    console.log(pair.getFirst()); // 输出:Hello
    console.log(pair.getSecond()); // 输出:42
    

    泛型的基本定义如下:

    function functionName<T>(arg: T): ReturnType {
      // 函数体
    }
    

    其中:

    • <T> 表示泛型参数的占位符,可以是任何有效的标识符。
    • arg: T 表示函数的参数类型为泛型参数 T
    • ReturnType 表示函数的返回类型,根据实际情况进行替换。

    通过泛型,可以在编写函数或类时保持灵活性,以便在不同的上下文中使用不同的类型,并提高代码的复用性和类型安全性。

  2. 联合类型(Union Types):联合类型允许将多个类型组合在一起。变量具有联合类型时,可以赋值给其中任何一个类型。例如:

let value: string | number;
value = "hello";
value = 42;
  1. 交叉类型(Intersection Types):交叉类型允许将多个类型合并成一个新类型。新类型将具有所有原始类型的特性。例如:
interface A {
  propA: number;
}

interface B {
  propB: string;
}

type C = A & B;

let obj: C = {
  propA: 42,
  propB: "hello",
};
  1. 类型别名(Type Aliases):类型别名允许为现有类型创建一个新名称。这在复杂类型或重复使用的类型上特别有用。例如:
type Point = {
  x: number;
  y: number;
};

let p: Point = { x: 10, y: 20 };
  1. 条件类型(Conditional Types):条件类型允许根据条件选择类型。通过使用条件类型,可以根据某些条件确定要返回的类型。例如:
type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  "unknown";

let name: TypeName<string>; // 类型为 "string"
let age: TypeName<number>; // 类型为 "number"
  1. 映射类型(Mapped Types):映射类型允许根据现有类型创建新类型,并根据需要修改其属性。通过使用映射类型,可以自动创建具有相同结构但具有不同类型的新类型。例如:
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

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

let readonlyPerson: Readonly<Person> = {
  name: "Alice",
  age: 25,
};

readonlyPerson.name = "Bob"; // 编译错误,属性为只读

泛型工具类型

在 TypeScript 中,有一些内置的泛型工具类型可用于操作和转换其他类型。以下是一些常用的泛型工具类型:

  1. Partial: 创建一个类型,其中所有属性都是可选的。它接受一个泛型参数 T,并将 T 中的所有属性设置为可选属性。示例:
interface Person {
  name: string;
  age: number;
}

type PartialPerson = Partial<Person>;

/*
PartialPerson 的类型为:
{
  name?: string;
  age?: number;
}
*/
  1. Readonly: 创建一个类型,其中所有属性都是只读的。它接受一个泛型参数 T,并将 T 中的所有属性设置为只读属性。示例:
interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

/*
ReadonlyPerson 的类型为:
{
  readonly name: string;
  readonly age: number;
}
*/
  1. Pick<T, K>: 从类型 T 中选择指定的属性 K,创建一个新类型。它接受两个泛型参数 TK,其中 T 是源类型,K 是要选择的属性名的联合类型。示例:
interface Person {
  name: string;
  age: number;
  address: string;
}

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

/*
PersonInfo 的类型为:
{
  name: string;
  age: number;
}
*/
  1. Omit<T, K>: 从类型 T 中排除指定的属性 K,创建一个新类型。它接受两个泛型参数 TK,其中 T 是源类型,K 是要排除的属性名的联合类型。示例:
interface Person {
  name: string;
  age: number;
  address: string;
}

type PersonWithoutAddress = Omit<Person, 'address'>;

/*
PersonWithoutAddress 的类型为:
{
  name: string;
  age: number;
}
*/
  1. Exclude<T, U>: 从类型 T 中排除可以赋值给类型 U 的部分,创建一个新类型。它接受两个泛型参数 TU,并返回 T 中不包含在 U 中的类型。示例:
type Numbers = 1 | 2 | 3 | 4 | 5;
type EvenNumbers = Exclude<Numbers, 1 | 3 | 5>; // EvenNumbers 的类型为 2 | 4

这些是 TypeScript 中的一些常用泛型工具类型,它们提供了方便的方式来操作和转换类型,以满足特定的需求。还有其他许多泛型工具类型可供使用,可以根据具体的情况选择适合的工具类型。

待更新......