Skip to content

TypeScript 面试题目

1. 什么是 TypeScript?它与 JavaScript 有什么关系?

Details

TypeScript 是 JavaScript 的超集,它扩展了 JavaScript 的功能,添加了静态类型检查和其他特性。TypeScript 最终会被编译成 JavaScript,因此可以在任何支持 JavaScript 的环境中运行。

TypeScript 与 JavaScript 的关系

  1. 超集关系:TypeScript 包含了 JavaScript 的所有功能,并且添加了额外的特性。
  2. 静态类型:TypeScript 引入了静态类型系统,可以在编译时捕获类型错误。
  3. 编译过程:TypeScript 代码需要通过 TypeScript 编译器(tsc)编译成 JavaScript 代码才能运行。
  4. 向后兼容:所有有效的 JavaScript 代码都是有效的 TypeScript 代码,因此可以逐步将 JavaScript 项目迁移到 TypeScript。

TypeScript 的主要特性

  • 静态类型:在编译时检查类型,减少运行时错误
  • 接口:定义对象的结构和类型
  • 泛型:支持代码复用和类型安全
  • :支持面向对象编程
  • 模块:支持模块化开发
  • 装饰器:支持元编程
  • 类型推断:自动推断变量类型,减少显式类型标注

示例代码

typescript
// TypeScript 代码
interface Person {
  name: string;
  age: number;
}

function greet(person: Person): string {
  return `Hello, ${person.name}! You are ${person.age} years old.`;
}

const user: Person = { name: "Alice", age: 30 };
console.log(greet(user));

// 编译后的 JavaScript 代码
function greet(person) {
  return `Hello, ${person.name}! You are ${person.age} years old.`;
}

const user = { name: "Alice", age: 30 };
console.log(greet(user));

2. TypeScript 的类型系统包括哪些基本类型?

Details

TypeScript 提供了丰富的类型系统,包括以下基本类型:

1. 原始类型

  • number:数值类型,包括整数和浮点数
  • string:字符串类型
  • boolean:布尔类型,值为 true 或 false
  • undefined:未定义类型
  • null:空值类型
  • symbol:符号类型(ES6+)
  • bigint:大整数类型(ES2020+)

2. 复合类型

  • object:对象类型,包括数组、函数、类实例等
  • array:数组类型,如 number[]Array<number>
  • tuple:元组类型,固定长度和类型的数组,如 [string, number]
  • enum:枚举类型,一组命名的常量
  • function:函数类型,包括参数类型和返回值类型

3. 特殊类型

  • any:任意类型,禁用类型检查
  • unknown:未知类型,需要类型断言才能使用
  • never:永不存在的值的类型
  • void:无返回值的类型

4. 高级类型

  • 联合类型:多个类型的组合,如 string | number
  • 交叉类型:多个类型的交集,如 A & B
  • 字面量类型:具体的字符串、数字或布尔值,如 "red" | "blue"
  • 类型别名:为类型创建新名称,如 type UserId = string | number
  • 接口:定义对象的结构和类型

示例代码

typescript
// 原始类型
let num: number = 10;
let str: string = "Hello";
let bool: boolean = true;
let undef: undefined = undefined;
let nul: null = null;
let sym: symbol = Symbol("key");
let big: bigint = 100n;

// 复合类型
let arr: number[] = [1, 2, 3];
let tuple: [string, number] = ["Alice", 30];
enum Color { Red, Green, Blue }
let color: Color = Color.Red;

// 特殊类型
let anyValue: any = "anything";
let unknownValue: unknown = "something";
let neverValue: never;
let voidFunction = (): void => console.log("Hello");

// 高级类型
let union: string | number = "test";
interface A { a: number }
interface B { b: string }
let intersection: A & B = { a: 1, b: "test" };
type Direction = "up" | "down" | "left" | "right";
type UserId = string | number;

3. 什么是接口(Interface)?它与类型别名(Type Alias)有什么区别?

Details

接口(Interface)是 TypeScript 中用于定义对象结构和类型的一种方式,它可以描述对象的形状、方法和属性。

接口的基本语法

typescript
interface Person {
  name: string;
  age: number;
  email?: string; // 可选属性
  readonly id: string; // 只读属性
  sayHello(): void; // 方法
}

const person: Person = {
  name: "Alice",
  age: 30,
  id: "123",
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

接口与类型别名的区别

  1. 声明合并:接口可以多次声明,会自动合并;类型别名不能多次声明。

    typescript
    // 接口可以合并
    interface Person {
      name: string;
    }
    
    interface Person {
      age: number;
    }
    
    // 等同于
    interface Person {
      name: string;
      age: number;
    }
    
    // 类型别名不能合并
    type Person = {
      name: string;
    };
    
    // 错误:重复声明类型别名
    type Person = {
      age: number;
    };
  2. 扩展方式:接口通过 extends 扩展;类型别名通过交叉类型 & 扩展。

    typescript
    // 接口扩展
    interface Animal {
      name: string;
    }
    
    interface Dog extends Animal {
      breed: string;
    }
    
    // 类型别名扩展
    type Animal = {
      name: string;
    };
    
    type Dog = Animal & {
      breed: string;
    };
  3. 实现方式:类可以实现接口;类不能实现类型别名(除非类型别名是对象类型)。

    typescript
    // 类实现接口
    interface Shape {
      draw(): void;
    }
    
    class Circle implements Shape {
      draw() {
        console.log("Drawing a circle");
      }
    }
    
    // 类实现类型别名(如果是对象类型)
    type Shape = {
      draw(): void;
    };
    
    class Circle implements Shape {
      draw() {
        console.log("Drawing a circle");
      }
    }
  4. 使用场景

    • 接口:用于定义对象的结构,适合用于类的实现和接口的扩展
    • 类型别名:用于定义复杂类型,如联合类型、交叉类型、元组类型等

4. 什么是泛型(Generics)?它的作用是什么?

Details

泛型(Generics)是 TypeScript 中一种强大的特性,允许在定义函数、接口或类时使用类型参数,使代码更加灵活和可复用。

泛型的基本语法

typescript
// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 使用泛型函数
let output1 = identity<string>("hello");
let output2 = identity<number>(10);

// 类型推断
let output3 = identity("world"); // 自动推断为 string 类型

泛型的作用

  1. 类型安全:在编译时检查类型,减少运行时错误
  2. 代码复用:编写通用的代码,适用于多种类型
  3. 灵活性:允许用户指定类型,而不是硬编码
  4. 可读性:使代码更加清晰,明确表达类型意图

泛型的高级用法

  1. 泛型接口

    typescript
    interface Container<T> {
      value: T;
      getValue(): T;
    }
    
    class Box<T> implements Container<T> {
      constructor(public value: T) {}
      getValue() { return this.value; }
    }
    
    let numberBox = new Box<number>(10);
    let stringBox = new Box<string>("hello");
  2. 泛型约束

    typescript
    interface Lengthwise {
      length: number;
    }
    
    function loggingIdentity<T extends Lengthwise>(arg: T): T {
      console.log(arg.length);
      return arg;
    }
    
    // 正确:string 有 length 属性
    loggingIdentity("hello");
    
    // 正确:数组有 length 属性
    loggingIdentity([1, 2, 3]);
    
    // 错误:number 没有 length 属性
    loggingIdentity(10);
  3. 泛型默认类型

    typescript
    function createArray<T = string>(length: number, value: T): T[] {
      return Array(length).fill(value);
    }
    
    // 使用默认类型 string
    let stringArray = createArray(3, "a");
    
    // 指定类型 number
    let numberArray = createArray<number>(3, 10);

泛型的实际应用

泛型在实际开发中非常常见,例如:

  • 集合类(如数组、Map、Set)
  • 状态管理库(如 Redux、MobX)
  • HTTP 客户端库(如 Axios)
  • 工具函数库(如 Lodash)

5. TypeScript 中的类型断言是什么?有哪些方式?

Details

类型断言(Type Assertion)是 TypeScript 中一种告诉编译器某个值的具体类型的方式,它不会在运行时做任何类型转换,只是在编译时告诉编译器如何处理类型。

类型断言的两种方式

  1. 尖括号语法

    typescript
    let someValue: any = "this is a string";
    let strLength: number = (<string>someValue).length;
  2. as 语法

    typescript
    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;

    注意:在 JSX 中,只能使用 as 语法,因为尖括号会被解析为 JSX 元素。

类型断言的使用场景

  1. 从 any 类型转换:当你知道一个 any 类型的变量的具体类型时,可以使用类型断言。

    typescript
    let userInput: any = "123";
    let numericInput: number = (userInput as string).length;
  2. 从联合类型转换:当你知道联合类型中的具体类型时,可以使用类型断言。

    typescript
    interface Cat {
      meow(): void;
    }
    
    interface Dog {
      bark(): void;
    }
    
    function petAnimal(animal: Cat | Dog) {
      if ((animal as Cat).meow) {
        (animal as Cat).meow();
      } else {
        (animal as Dog).bark();
      }
    }
  3. 从父类型转换为子类型:当你知道一个变量的实际类型是子类型时,可以使用类型断言。

    typescript
    class Animal {
      name: string;
    }
    
    class Dog extends Animal {
      bark(): void {
        console.log("Woof!");
      }
    }
    
    let animal: Animal = new Dog();
    (animal as Dog).bark();

类型断言的注意事项

  1. 类型断言不是类型转换:它不会在运行时改变变量的类型,只是在编译时告诉编译器如何处理类型。
  2. 类型断言要谨慎使用:如果断言的类型与实际类型不符,可能会导致运行时错误。
  3. 优先使用类型守卫:对于联合类型,优先使用类型守卫(如 typeofinstanceof、自定义类型守卫),而不是类型断言。

6. TypeScript 中的枚举(Enum)是什么?有什么特点?

Details

枚举(Enum)是 TypeScript 中一种特殊的类型,用于定义一组命名的常量。枚举可以使代码更加清晰和易于维护,特别是当有一组相关的常量时。

枚举的基本语法

typescript
// 数字枚举
enum Direction {
  Up,
  Down,
  Left,
  Right
}

// 使用枚举
let direction: Direction = Direction.Up;
console.log(direction); // 输出:0

// 字符串枚举
enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}

// 使用字符串枚举
let color: Color = Color.Red;
console.log(color); // 输出:"RED"

// 异构枚举(不推荐)
enum Mixed {
  No = 0,
  Yes = "YES"
}

枚举的特点

  1. 默认值:数字枚举的默认值从 0 开始,依次递增。

    typescript
    enum Direction {
      Up, // 0
      Down, // 1
      Left, // 2
      Right // 3
    }
  2. 自定义值:可以为枚举成员指定自定义值。

    typescript
    enum Direction {
      Up = 1,
      Down,
      Left,
      Right
    }
    // Up: 1, Down: 2, Left: 3, Right: 4
  3. 反向映射:数字枚举支持反向映射,可以通过值获取枚举成员的名称。

    typescript
    enum Direction {
      Up,
      Down,
      Left,
      Right
    }
    
    console.log(Direction[0]); // 输出:"Up"
    console.log(Direction[1]); // 输出:"Down"
  4. 常量枚举:使用 const enum 可以在编译时内联枚举值,提高性能。

    typescript
    const enum Direction {
      Up,
      Down,
      Left,
      Right
    }
    
    let direction = Direction.Up; // 编译为:let direction = 0;
  5. 外部枚举:使用 declare enum 可以声明一个枚举,但不生成代码,用于描述外部库中的枚举。

    typescript
    declare enum Direction {
      Up,
      Down,
      Left,
      Right
    }

枚举的使用场景

  1. 状态管理:定义应用中的各种状态
  2. 错误码:定义错误类型和错误码
  3. 配置选项:定义配置项的可能值
  4. 方向、颜色等常量:定义一组相关的常量

枚举的优缺点

优点

  • 代码更加清晰,易于理解
  • 提供了类型安全
  • 支持反向映射(数字枚举)

缺点

  • 会生成额外的代码(非 const enum)
  • 字符串枚举不支持反向映射
  • 枚举成员的值是固定的,不能动态修改

7. TypeScript 中的装饰器(Decorator)是什么?有哪些类型?

Details

装饰器(Decorator)是 TypeScript 中一种特殊的语法,用于修改类、方法、属性或参数的行为。装饰器是一种元编程特性,允许在不修改原始代码的情况下增强类和类成员。

装饰器的基本语法

装饰器使用 @ 符号后跟装饰器名称的语法:

typescript
@decorator
class MyClass {
  @propertyDecorator
  property: string;

  @methodDecorator
  method(@parameterDecorator param: string) {
    // 方法体
  }
}

装饰器的类型

  1. 类装饰器:应用于类的声明

    typescript
    function sealed(constructor: Function) {
      Object.seal(constructor);
      Object.seal(constructor.prototype);
    }
    
    @sealed
    class Greeter {
      greeting: string;
      constructor(message: string) {
        this.greeting = message;
      }
      greet() {
        return "Hello, " + this.greeting;
      }
    }
  2. 方法装饰器:应用于方法的声明

    typescript
    function enumerable(value: boolean) {
      return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
      };
    }
    
    class Greeter {
      greeting: string;
      constructor(message: string) {
        this.greeting = message;
      }
    
      @enumerable(false)
      greet() {
        return "Hello, " + this.greeting;
      }
    }
  3. 属性装饰器:应用于属性的声明

    typescript
    function configurable(value: boolean) {
      return function (target: any, propertyKey: string) {
        const descriptor: PropertyDescriptor = {
          configurable: value
        };
        return descriptor;
      };
    }
    
    class Greeter {
      @configurable(false)
      greeting: string;
      constructor(message: string) {
        this.greeting = message;
      }
      greet() {
        return "Hello, " + this.greeting;
      }
    }
  4. 参数装饰器:应用于参数的声明

    typescript
    function required(target: any, propertyKey: string, parameterIndex: number) {
      console.log(`Parameter ${parameterIndex} of ${propertyKey} is required`);
    }
    
    class Greeter {
      greeting: string;
      constructor(message: string) {
        this.greeting = message;
      }
      greet(@required name: string) {
        return "Hello, " + name + ", " + this.greeting;
      }
    }

装饰器的执行顺序

装饰器的执行顺序如下:

  1. 参数装饰器,然后是方法装饰器、访问器装饰器、属性装饰器
  2. 类装饰器

装饰器的应用场景

  1. 日志记录:记录方法的调用和参数
  2. 性能监控:测量方法的执行时间
  3. 权限控制:检查用户是否有权限执行某个方法
  4. 依赖注入:自动注入依赖
  5. 序列化/反序列化:自动处理对象的序列化和反序列化

装饰器的配置

要使用装饰器,需要在 tsconfig.json 中启用装饰器特性:

json
{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

8. TypeScript 中的模块(Module)和命名空间(Namespace)有什么区别?

Details

TypeScript 中的模块(Module)和命名空间(Namespace)都是用于组织代码的方式,但它们有一些重要的区别。

模块(Module)

模块是 TypeScript 中组织代码的主要方式,它使用 ES6 模块语法(importexport)。

模块的特点

  1. 文件级作用域:每个模块都有自己的作用域,模块内的声明不会污染全局作用域
  2. 显式导出:需要使用 export 关键字导出模块成员
  3. 显式导入:需要使用 import 关键字导入模块成员
  4. 支持静态分析:TypeScript 编译器可以分析模块依赖
  5. 支持树摇(Tree Shaking):未使用的模块成员会被移除

模块的示例

typescript
// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

// app.ts
import { add, subtract } from "./math";

console.log(add(1, 2)); // 3
console.log(subtract(3, 1)); // 2

命名空间(Namespace)

命名空间是 TypeScript 中另一种组织代码的方式,它使用 namespace 关键字定义。

命名空间的特点

  1. 全局作用域:命名空间是全局作用域的一部分
  2. 隐式导出:命名空间内的声明默认是可见的
  3. 使用点语法访问:使用 NamespaceName.MemberName 的方式访问成员
  4. 支持合并:同名命名空间会自动合并
  5. 支持嵌套:可以在命名空间内定义子命名空间

命名空间的示例

typescript
// math.ts
namespace Math {
  export function add(a: number, b: number): number {
    return a + b;
  }
  
  export function subtract(a: number, b: number): number {
    return a - b;
  }
}

// app.ts
/// <reference path="./math.ts" />

console.log(Math.add(1, 2)); // 3
console.log(Math.subtract(3, 1)); // 2

模块与命名空间的区别

特性模块命名空间
作用域文件级全局级
导出方式显式(使用 export显式(使用 export
导入方式使用 import使用 /// <reference> 或全局访问
合并不支持支持
树摇支持不支持
推荐使用是(现代 TypeScript 项目)否(主要用于旧代码)

最佳实践

在现代 TypeScript 项目中,推荐使用模块而不是命名空间,因为:

  1. 模块与 ES6 模块标准兼容
  2. 模块支持树摇,减少打包体积
  3. 模块提供更好的代码组织和封装
  4. 模块是现代前端工具链(如 Webpack、Rollup)的标准选择

9. TypeScript 中的类型守卫(Type Guard)是什么?有哪些类型?

Details

类型守卫(Type Guard)是 TypeScript 中一种用于在运行时检查类型的机制,它可以帮助编译器在编译时确定变量的具体类型。

类型守卫的作用

类型守卫的主要作用是在运行时检查变量的类型,从而在编译时提供更准确的类型推断。这使得 TypeScript 能够在编译时捕获更多的类型错误,同时保持代码的灵活性。

类型守卫的类型

  1. typeof 类型守卫:用于检查原始类型

    typescript
    function processValue(value: string | number) {
      if (typeof value === "string") {
        // 这里 value 被推断为 string 类型
        return value.toUpperCase();
      } else {
        // 这里 value 被推断为 number 类型
        return value.toFixed(2);
      }
    }
  2. instanceof 类型守卫:用于检查对象是否是某个类的实例

    typescript
    class Animal {
      name: string;
    }
    
    class Dog extends Animal {
      bark() {
        console.log("Woof!");
      }
    }
    
    class Cat extends Animal {
      meow() {
        console.log("Meow!");
      }
    }
    
    function processAnimal(animal: Animal) {
      if (animal instanceof Dog) {
        // 这里 animal 被推断为 Dog 类型
        animal.bark();
      } else if (animal instanceof Cat) {
        // 这里 animal 被推断为 Cat 类型
        animal.meow();
      }
    }
  3. in 类型守卫:用于检查对象是否具有某个属性

    typescript
    interface Cat {
      name: string;
      meow(): void;
    }
    
    interface Dog {
      name: string;
      bark(): void;
    }
    
    function processPet(pet: Cat | Dog) {
      if ("meow" in pet) {
        // 这里 pet 被推断为 Cat 类型
        pet.meow();
      } else {
        // 这里 pet 被推断为 Dog 类型
        pet.bark();
      }
    }
  4. 自定义类型守卫:使用 is 关键字定义自定义类型守卫

    typescript
    interface Cat {
      name: string;
      meow(): void;
    }
    
    interface Dog {
      name: string;
      bark(): void;
    }
    
    function isCat(pet: Cat | Dog): pet is Cat {
      return (pet as Cat).meow !== undefined;
    }
    
    function processPet(pet: Cat | Dog) {
      if (isCat(pet)) {
        // 这里 pet 被推断为 Cat 类型
        pet.meow();
      } else {
        // 这里 pet 被推断为 Dog 类型
        pet.bark();
      }
    }
  5. 字面量类型守卫:用于检查字符串或数字字面量类型

    typescript
    type Direction = "up" | "down" | "left" | "right";
    
    function processDirection(direction: Direction) {
      if (direction === "up") {
        // 这里 direction 被推断为 "up" 类型
        console.log("Going up!");
      } else if (direction === "down") {
        // 这里 direction 被推断为 "down" 类型
        console.log("Going down!");
      }
    }

类型守卫的应用场景

  1. 联合类型处理:当变量是联合类型时,使用类型守卫可以确定其具体类型
  2. 类型安全:在运行时检查类型,确保代码的类型安全
  3. 代码可读性:使代码更加清晰,明确表达类型检查的意图
  4. 减少类型断言:减少使用类型断言的需要,使代码更加类型安全

10. TypeScript 中的高级类型有哪些?

Details

TypeScript 提供了多种高级类型,用于处理复杂的类型场景。以下是一些常用的高级类型:

1. 交叉类型(Intersection Types)

交叉类型将多个类型合并为一个类型,新类型包含所有类型的特性。

typescript
interface A {
  a: number;
}

interface B {
  b: string;
}

type C = A & B;

let c: C = {
  a: 1,
  b: "test"
};

2. 联合类型(Union Types)

联合类型表示一个值可以是多个类型中的一个。

typescript
type StringOrNumber = string | number;

let value: StringOrNumber = "test";
value = 10;

3. 类型守卫(Type Guards)

类型守卫用于在运行时检查类型,详见前面的问题。

4. 类型别名(Type Aliases)

类型别名为类型创建新名称,使代码更加清晰。

typescript
type UserId = string | number;
type Direction = "up" | "down" | "left" | "right";

5. 字符串字面量类型(String Literal Types)

字符串字面量类型表示具体的字符串值。

typescript
type Color = "red" | "green" | "blue";

let color: Color = "red";
// 错误:"yellow" 不是有效的 Color 类型
// color = "yellow";

6. 数字字面量类型(Number Literal Types)

数字字面量类型表示具体的数字值。

typescript
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

let roll: DiceRoll = 3;
// 错误:7 不是有效的 DiceRoll 类型
// roll = 7;

7. 映射类型(Mapped Types)

映射类型用于基于现有类型创建新类型。

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

// 只读版本
type ReadonlyPerson = { readonly [K in keyof Person]: Person[K] };

// 可选版本
type OptionalPerson = { [K in keyof Person]?: Person[K] };

// 部分类型
type PartialPerson = Partial<Person>;

// 只读类型
type ReadonlyPerson = Readonly<Person>;

// 选取类型
type PickPerson = Pick<Person, "name">;

// 排除类型
type OmitPerson = Omit<Person, "age">;

8. 条件类型(Conditional Types)

条件类型根据条件选择类型。

typescript
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

// 提取类型
type ExtractString<T> = T extends string ? T : never;
type C = ExtractString<string | number>; // string

// 排除类型
type ExcludeString<T> = T extends string ? never : T;
type D = ExcludeString<string | number>; // number

9. 推断类型(Infer Types)

推断类型用于从类型中提取类型信息。

typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

function foo(): string {
  return "test";
}

type FooReturn = ReturnType<typeof foo>; // string

// 推断数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : T;
type E = ElementType<string[]>; // string
type F = ElementType<number>; // number

10. 模板字面量类型(Template Literal Types)

模板字面量类型用于创建基于字符串模板的类型。

typescript
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type MouseEvent = EventName<"mouseover">; // "onMouseover"

// 组合模板字面量类型
type Path = "user" | "post" | "comment";
type Route = `/${Path}`; // "/user" | "/post" | "/comment"

11. 递归类型(Recursive Types)

递归类型用于定义自引用的类型。

typescript
type NestedObject = {
  [key: string]: string | number | NestedObject;
};

const obj: NestedObject = {
  name: "Alice",
  age: 30,
  address: {
    street: "123 Main St",
    city: "New York"
  }
};

12. 索引类型(Indexed Types)

索引类型用于访问对象的属性类型。

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

// 所有属性名的联合类型
type PersonKeys = keyof Person; // "name" | "age"

// 属性值的类型
type PersonValues = Person[keyof Person]; // string | number

// 动态访问属性类型
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person: Person = { name: "Alice", age: 30 };
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number

11. TypeScript 中的 anyunknownnevervoid 类型有什么区别?

Details

TypeScript 中有几种特殊类型,它们各有不同的用途和特点:

1. any 类型

any 类型表示任意类型,它会禁用 TypeScript 的类型检查。

特点

  • 可以赋值给任何类型
  • 任何类型可以赋值给 any
  • 可以访问任何属性和方法,不会有类型检查
  • 编译时不会报错,运行时可能会出错

使用场景

  • 当你不确定变量的类型时
  • 当你想禁用类型检查时
  • 当你与 JavaScript 代码交互时
typescript
let anyValue: any = "hello";
anyValue = 10; // 可以
anyValue = true; // 可以
anyValue.foo(); // 编译时不会报错,运行时可能会出错

2. unknown 类型

unknown 类型表示未知类型,它是 TypeScript 3.0 引入的类型。

特点

  • 任何类型可以赋值给 unknown
  • unknown 不能赋值给其他类型(除非使用类型断言)
  • 不能直接访问 unknown 类型的属性和方法
  • 需要类型检查或类型断言才能使用

使用场景

  • 当你不确定变量的类型时
  • 当你想进行类型检查时
  • 当你想替代 any 类型时
typescript
let unknownValue: unknown = "hello";
unknownValue = 10; // 可以

// 错误:不能直接赋值给 string 类型
// let str: string = unknownValue;

// 正确:使用类型断言
let str: string = unknownValue as string;

// 正确:使用类型检查
if (typeof unknownValue === "string") {
  let str: string = unknownValue;
}

3. never 类型

never 类型表示永不存在的值的类型。

特点

  • 没有值可以赋值给 never 类型
  • never 类型可以赋值给任何类型
  • 通常用于表示函数永远不会返回
  • 用于类型收窄

使用场景

  • 函数抛出异常
  • 函数无限循环
  • 类型收窄的最终情况
typescript
// 函数抛出异常
function throwError(message: string): never {
  throw new Error(message);
}

// 函数无限循环
function infiniteLoop(): never {
  while (true) {
    // 无限循环
  }
}

// 类型收窄
function processValue(value: string | number) {
  if (typeof value === "string") {
    // 处理字符串
  } else if (typeof value === "number") {
    // 处理数字
  } else {
    // 这里 value 的类型是 never
    const neverValue: never = value;
  }
}

4. void 类型

void 类型表示函数没有返回值。

特点

  • 函数返回 undefined
  • 可以赋值 undefinednull(在 strictNullChecks 为 false 时)
  • 通常用于函数返回类型

使用场景

  • 函数没有返回值
  • 函数返回 undefined
typescript
function logMessage(message: string): void {
  console.log(message);
  // 没有 return 语句,默认返回 undefined
}

function returnUndefined(): void {
  return undefined;
}

// 错误:不能返回其他值
// function returnValue(): void {
//   return "hello";
// }

总结

类型描述可赋值给可从赋值用途
any任意类型任何类型任何类型禁用类型检查
unknown未知类型只有 any任何类型类型安全的 any
never永不存在的值任何类型没有类型表示函数永不返回
void无返回值anyunknownundefinednull函数没有返回值

12. TypeScript 中的配置文件 tsconfig.json 有哪些重要配置项?

Details

tsconfig.json 是 TypeScript 项目的配置文件,用于指定编译选项和项目设置。以下是一些重要的配置项:

1. 编译选项(compilerOptions)

目标版本(target)

指定编译后的 JavaScript 版本。

json
{
  "compilerOptions": {
    "target": "ES5" // 可选值:ES3, ES5, ES6/ES2015, ES7/ES2016, ES8/ES2017, ES9/ES2018, ES10/ES2019, ES2020, ES2021, ES2022, ESNext
  }
}

模块系统(module)

指定生成的模块系统。

json
{
  "compilerOptions": {
    "module": "CommonJS" // 可选值:CommonJS, AMD, UMD, System, ES2015, ES2020, ESNext, None
  }
}

模块解析策略(moduleResolution)

指定模块解析策略。

json
{
  "compilerOptions": {
    "moduleResolution": "node" // 可选值:node, classic
  }
}

严格模式(strict)

启用所有严格类型检查选项。

json
{
  "compilerOptions": {
    "strict": true // 等价于启用所有严格选项
  }
}

严格空检查(strictNullChecks)

启用严格的空值检查。

json
{
  "compilerOptions": {
    "strictNullChecks": true // 不允许 null 和 undefined 赋值给非空类型
  }
}

严格函数类型(strictFunctionTypes)

启用严格的函数类型检查。

json
{
  "compilerOptions": {
    "strictFunctionTypes": true // 函数参数类型逆变,返回值类型协变
  }
}

严格绑定检查(strictBindCallApply)

启用严格的 bind、call 和 apply 方法检查。

json
{
  "compilerOptions": {
    "strictBindCallApply": true // 检查 bind、call 和 apply 的参数类型
  }
}

严格属性初始化(strictPropertyInitialization)

启用严格的属性初始化检查。

json
{
  "compilerOptions": {
    "strictPropertyInitialization": true // 检查类属性是否在构造函数中初始化
  }
}

无隐式 any(noImplicitAny)

禁止隐式 any 类型。

json
{
  "compilerOptions": {
    "noImplicitAny": true // 不允许隐式 any 类型
  }
}

无隐式返回(noImplicitReturns)

禁止函数隐式返回。

json
{
  "compilerOptions": {
    "noImplicitReturns": true // 要求函数的所有路径都有返回值
  }
}

无隐式 this(noImplicitThis)

禁止隐式 this 类型。

json
{
  "compilerOptions": {
    "noImplicitThis": true // 不允许隐式 this 类型
  }
}

始终严格模式(alwaysStrict)

在编译后的 JavaScript 文件中添加 "use strict"。

json
{
  "compilerOptions": {
    "alwaysStrict": true // 在输出文件中添加 "use strict"
  }
}

源映射(sourceMap)

生成源映射文件,用于调试。

json
{
  "compilerOptions": {
    "sourceMap": true // 生成 .map 文件
  }
}

输出目录(outDir)

指定编译输出的目录。

json
{
  "compilerOptions": {
    "outDir": "./dist" // 编译输出到 dist 目录
  }
}

根目录(rootDir)

指定源代码的根目录。

json
{
  "compilerOptions": {
    "rootDir": "./src" // 源代码在 src 目录
  }
}

声明文件(declaration)

生成 .d.ts 声明文件。

json
{
  "compilerOptions": {
    "declaration": true // 生成声明文件
  }
}

声明文件目录(declarationDir)

指定声明文件的输出目录。

json
{
  "compilerOptions": {
    "declarationDir": "./types" // 声明文件输出到 types 目录
  }
}

模块目标(moduleTarget)

指定模块目标。

json
{
  "compilerOptions": {
    "moduleTarget": "es2015" // 模块目标版本
  }
}

实验性装饰器(experimentalDecorators)

启用实验性装饰器特性。

json
{
  "compilerOptions": {
    "experimentalDecorators": true // 启用装饰器
  }
}

装饰器元数据(emitDecoratorMetadata)

启用装饰器元数据。

json
{
  "compilerOptions": {
    "emitDecoratorMetadata": true // 启用装饰器元数据
  }
}

2. 包含和排除(include, exclude, files)

包含(include)

指定要包含的文件或目录。

json
{
  "include": ["src/**/*"] // 包含 src 目录下的所有文件
}

排除(exclude)

指定要排除的文件或目录。

json
{
  "exclude": ["node_modules", "dist"] // 排除 node_modules 和 dist 目录
}

文件(files)

指定要编译的具体文件。

json
{
  "files": ["src/index.ts", "src/app.ts"] // 只编译指定的文件
}

3. 继承(extends)

继承其他 tsconfig.json 文件。

json
{
  "extends": "./tsconfig.base.json" // 继承基础配置
}

4. 引用(references)

引用其他 TypeScript 项目。

json
{
  "references": [
    { "path": "./../common" },
    { "path": "./../utils" }
  ]
}

5. 示例配置

json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "CommonJS",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "sourceMap": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

13. TypeScript 中的 tsc 命令有哪些常用选项?

Details

tsc 是 TypeScript 编译器的命令行工具,用于将 TypeScript 代码编译为 JavaScript 代码。以下是一些常用的 tsc 命令选项:

1. 基本选项

--help-h

显示帮助信息。

bash
tsc --help

--version-v

显示 TypeScript 版本。

bash
tsc --version

--init

初始化一个新的 TypeScript 项目,生成 tsconfig.json 文件。

bash
tsc --init

--project-p

指定 tsconfig.json 文件的路径。

bash
tsc --project ./tsconfig.json

--watch-w

启用监视模式,当文件变化时自动重新编译。

bash
tsc --watch

2. 编译选项

--target-t

指定编译目标 JavaScript 版本。

bash
tsc --target ES5

--module-m

指定生成的模块系统。

bash
tsc --module CommonJS

--outDir-d

指定输出目录。

bash
tsc --outDir ./dist

--outFile-o

将所有输出文件合并为一个文件。

bash
tsc --outFile ./dist/bundle.js

--sourceMap-s

生成源映射文件。

bash
tsc --sourceMap

--declaration-d

生成 .d.ts 声明文件。

bash
tsc --declaration

--declarationDir

指定声明文件的输出目录。

bash
tsc --declaration --declarationDir ./types

--noEmit

只进行类型检查,不生成输出文件。

bash
tsc --noEmit

--noEmitOnError

如果有错误,不生成输出文件。

bash
tsc --noEmitOnError

--strict

启用所有严格类型检查选项。

bash
tsc --strict

--strictNullChecks

启用严格的空值检查。

bash
tsc --strictNullChecks

--noImplicitAny

禁止隐式 any 类型。

bash
tsc --noImplicitAny

--noImplicitReturns

禁止函数隐式返回。

bash
tsc --noImplicitReturns

--noImplicitThis

禁止隐式 this 类型。

bash
tsc --noImplicitThis

--alwaysStrict

在编译后的 JavaScript 文件中添加 "use strict"。

bash
tsc --alwaysStrict

--experimentalDecorators

启用实验性装饰器特性。

bash
tsc --experimentalDecorators

--emitDecoratorMetadata

启用装饰器元数据。

bash
tsc --emitDecoratorMetadata

3. 其他选项

--listFiles

列出将要编译的文件。

bash
tsc --listFiles

--listFilesOnly

只列出将要编译的文件,不进行编译。

bash
tsc --listFilesOnly

--traceResolution

跟踪模块解析过程。

bash
tsc --traceResolution

--extendedDiagnostics

显示详细的诊断信息。

bash
tsc --extendedDiagnostics

--pretty

美化错误信息。

bash
tsc --pretty

4. 示例命令

bash
# 编译单个文件
tsc index.ts

# 编译多个文件
tsc file1.ts file2.ts file3.ts

# 编译整个项目(使用 tsconfig.json)
tsc

# 监视模式编译
tsc --watch

# 只进行类型检查,不生成文件
tsc --noEmit

# 指定输出目录
tsc --outDir ./dist

# 生成源映射和声明文件
tsc --sourceMap --declaration

# 启用严格模式
tsc --strict

14. TypeScript 中的类型推断是如何工作的?

Details

类型推断是 TypeScript 中一种强大的特性,它允许编译器根据上下文自动推断变量的类型,而不需要显式指定类型。

类型推断的基本原理

TypeScript 编译器会根据变量的初始值、函数的返回值、表达式的类型等信息,自动推断变量的类型。

类型推断的场景

  1. 变量初始化

    typescript
    let x = 10; // 推断为 number 类型
    let y = "hello"; // 推断为 string 类型
    let z = true; // 推断为 boolean 类型
  2. 函数返回值

    typescript
    function add(a: number, b: number) {
      return a + b; // 推断返回值为 number 类型
    }
    
    function greet(name: string) {
      return `Hello, ${name}!`; // 推断返回值为 string 类型
    }
  3. 函数参数

    typescript
    // 推断参数 x 为 number 类型,参数 y 为 number 类型
    const add = (x, y) => x + y;
  4. 数组和对象

    typescript
    // 推断为 number[] 类型
    let numbers = [1, 2, 3, 4, 5];
    
    // 推断为 { name: string; age: number } 类型
    let person = { name: "Alice", age: 30 };
  5. 类型守卫

    typescript
    function processValue(value: string | number) {
      if (typeof value === "string") {
        // 推断为 string 类型
        return value.toUpperCase();
      } else {
        // 推断为 number 类型
        return value.toFixed(2);
      }
    }
  6. 泛型推断

    typescript
    function identity<T>(arg: T): T {
      return arg;
    }
    
    // 推断 T 为 string 类型
    let result = identity("hello");
    
    // 推断 T 为 number 类型
    let result2 = identity(10);

类型推断的规则

  1. 最佳通用类型:当推断数组或联合类型时,TypeScript 会寻找最适合的通用类型。

    typescript
    // 推断为 (string | number)[] 类型
    let values = [1, "hello", 2, "world"];
  2. 上下文类型:当变量在特定上下文中使用时,TypeScript 会根据上下文推断类型。

    typescript
    window.onmousedown = function(event) {
      // 推断 event 为 MouseEvent 类型
      console.log(event.button);
    };
  3. 类型兼容性:TypeScript 会根据类型兼容性规则推断类型。

    typescript
    interface Animal {
      name: string;
    }
    
    interface Dog extends Animal {
      bark(): void;
    }
    
    // 推断为 Animal 类型
    let animal: Animal = { name: "Fido" };
    
    // 推断为 Dog 类型
    let dog: Dog = { name: "Fido", bark: () => console.log("Woof!") };
    
    // 正确:Dog 是 Animal 的子类型
    animal = dog;

类型推断的局限性

  1. 复杂表达式:对于复杂的表达式,TypeScript 可能无法准确推断类型。

    typescript
    // 可能推断为 any 类型
    let complex = someFunction() && anotherFunction();
  2. 函数重载:对于函数重载,TypeScript 可能无法推断正确的类型。

    typescript
    function foo(x: string): string;
    function foo(x: number): number;
    function foo(x: any): any {
      return x;
    }
    
    // 推断为 any 类型
    let result = foo("hello");
  3. 动态类型:对于动态生成的类型,TypeScript 无法推断类型。

    typescript
    // 推断为 any 类型
    let dynamic = eval("({ name: 'Alice' })");

类型推断的最佳实践

  1. 显式类型标注:对于重要的变量和函数参数,建议显式标注类型,提高代码可读性和可维护性。

  2. 利用类型推断:对于简单的变量和函数,可以利用类型推断,减少冗余的类型标注。

  3. 类型断言:当 TypeScript 无法正确推断类型时,可以使用类型断言。

  4. 类型守卫:对于联合类型,使用类型守卫可以帮助 TypeScript 推断更准确的类型。

15. TypeScript 中的接口与类有什么区别?

Details

接口(Interface)和类(Class)是 TypeScript 中两个重要的概念,它们有不同的用途和特点。

接口(Interface)

接口是 TypeScript 中用于定义对象结构和类型的一种方式,它只定义结构,不包含实现。

接口的特点

  1. 只定义结构:接口只定义对象的结构和类型,不包含具体的实现。
  2. 不能实例化:接口不能被实例化,只能被类实现或被其他接口扩展。
  3. 支持可选属性:接口可以定义可选属性,使用 ? 符号。
  4. 支持只读属性:接口可以定义只读属性,使用 readonly 关键字。
  5. 支持方法签名:接口可以定义方法签名,但不包含方法体。
  6. 支持索引签名:接口可以定义索引签名,用于访问对象的属性。
  7. 支持合并:同名接口会自动合并。

接口的示例

typescript
interface Person {
  readonly id: string;
  name: string;
  age: number;
  email?: string;
  sayHello(): void;
}

class Employee implements Person {
  id: string;
  name: string;
  age: number;
  email?: string;

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

  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

类(Class)

类是 TypeScript 中用于创建对象的蓝图,它包含属性和方法的实现。

类的特点

  1. 包含实现:类包含属性和方法的具体实现。
  2. 可以实例化:类可以使用 new 关键字实例化。
  3. 支持继承:类可以继承其他类,使用 extends 关键字。
  4. 支持访问修饰符:类的成员可以使用 publicprivateprotected 等访问修饰符。
  5. 支持构造函数:类可以定义构造函数,用于初始化对象。
  6. 支持静态成员:类可以定义静态属性和方法,使用 static 关键字。
  7. 支持抽象类:类可以是抽象的,使用 abstract 关键字,不能直接实例化。

类的示例

typescript
class Person {
  protected name: string;
  private age: number;
  public email?: string;

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

  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }

  static create(name: string, age: number, email?: string): Person {
    return new Person(name, age, email);
  }
}

class Employee extends Person {
  private id: string;

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

  sayHello() {
    console.log(`Hello, my name is ${this.name}, my employee ID is ${this.id}`);
  }
}

const person = new Person("Alice", 30);
const employee = new Employee("123", "Bob", 25);

接口与类的区别

特性接口
实现只定义结构,不包含实现包含属性和方法的实现
实例化不能实例化可以实例化
继承可以被其他接口扩展(extends)可以继承其他类(extends)
实现可以被类实现(implements)不能被其他类实现
访问修饰符不支持访问修饰符支持访问修饰符(public、private、protected)
构造函数不支持构造函数支持构造函数
静态成员不支持静态成员支持静态成员
抽象类不支持支持抽象类
合并支持同名接口合并不支持同名类合并

接口与类的使用场景

  1. 接口

    • 定义对象的结构和类型
    • 作为类的实现契约
    • 定义函数的参数和返回值类型
    • 定义模块的导出类型
    • 创建对象的蓝图
    • 实现具体的业务逻辑
    • 封装数据和行为
    • 实现继承和多态

最佳实践

  1. 接口用于定义契约:使用接口定义对象的结构和类型,作为类的实现契约。

  2. 类用于实现逻辑:使用类实现具体的业务逻辑,封装数据和行为。

  3. 接口与类结合使用:使用接口定义类的结构,使用类实现具体的逻辑。

    typescript
    // 定义接口
    interface User {
      id: string;
      name: string;
      email: string;
      login(): void;
    }
    
    // 实现接口
    class Customer implements User {
      id: string;
      name: string;
      email: string;
      
      constructor(id: string, name: string, email: string) {
        this.id = id;
        this.name = name;
        this.email = email;
      }
      
      login() {
        console.log(`${this.name} logged in`);
      }
    }

16. TypeScript 与 React 如何集成?

Details

TypeScript 与 React 可以很好地集成,提供类型安全和更好的开发体验。以下是 TypeScript 与 React 集成的主要方式:

1. 项目初始化

使用 Vite 或 Create React App 创建 TypeScript 项目:

bash
# 使用 Vite
npm create vite@latest my-react-app -- --template react-ts

# 使用 Create React App
npx create-react-app my-react-app --template typescript

2. 组件类型定义

函数组件

使用 FC(Function Component)类型:

typescript
import React, { FC, useState, useEffect } from 'react';

interface Props {
  title: string;
  subtitle?: string;
  onButtonClick: (id: number) => void;
}

const MyComponent: FC<Props> = ({ title, subtitle, onButtonClick }) => {
  const [count, setCount] = useState<number>(0);
  
  useEffect(() => {
    console.log('Component mounted');
    return () => console.log('Component unmounted');
  }, []);
  
  return (
    <div>
      <h1>{title}</h1>
      {subtitle && <h2>{subtitle}</h2>}
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => onButtonClick(count)}>Submit</button>
    </div>
  );
};

export default MyComponent;

类组件

typescript
import React, { Component } from 'react';

interface Props {
  title: string;
}

interface State {
  count: number;
}

class MyComponent extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  
  render() {
    return (
      <div>
        <h1>{this.props.title}</h1>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

export default MyComponent;

3. 类型定义文件

对于第三方库,可能需要安装对应的类型定义文件:

bash
# 安装 React 类型定义
npm install --save-dev @types/react @types/react-dom

# 安装其他库的类型定义
npm install --save-dev @types/lodash

4. 自定义钩子类型

typescript
import { useState, useCallback, useMemo, useRef, useEffect } from 'react';

// 自定义钩子类型
type UseCounterReturn = {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
};

export const useCounter = (initialValue: number = 0): UseCounterReturn => {
  const [count, setCount] = useState<number>(initialValue);
  
  const increment = useCallback(() => setCount(c => c + 1), []);
  const decrement = useCallback(() => setCount(c => c - 1), []);
  const reset = useCallback(() => setCount(initialValue), [initialValue]);
  
  return {
    count,
    increment,
    decrement,
    reset
  };
};

5. 事件处理类型

typescript
import React, { FC } from 'react';

const MyComponent: FC = () => {
  // 鼠标事件类型
  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    console.log(e.clientX, e.clientY);
  };
  
  // 键盘事件类型
  const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  };
  
  // 表单事件类型
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log('Form submitted');
  };
  
  return (
    <div onMouseMove={handleMouseMove}>
      <form onSubmit={handleSubmit}>
        <input 
          type="text" 
          onKeyPress={handleKeyPress}
          placeholder="Type something..."
        />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

export default MyComponent;

6. 上下文(Context)类型

typescript
import React, { createContext, useContext, FC, ReactNode } from 'react';

interface User {
  id: string;
  name: string;
  email: string;
}

interface UserContextType {
  user: User | null;
  setUser: (user: User | null) => void;
}

// 创建上下文
const UserContext = createContext<UserContextType | undefined>(undefined);

// 上下文提供者
interface UserProviderProps {
  children: ReactNode;
}

export const UserProvider: FC<UserProviderProps> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};

// 自定义钩子
export const useUser = () => {
  const context = useContext(UserContext);
  if (context === undefined) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
};

7. 最佳实践

  1. 使用泛型组件

    typescript
    interface ListProps<T> {
      items: T[];
      renderItem: (item: T, index: number) => React.ReactNode;
    }
    
    function List<T>({ items, renderItem }: ListProps<T>) {
      return (
        <ul>
          {items.map((item, index) => (
            <li key={index}>{renderItem(item, index)}</li>
          ))}
        </ul>
      );
    }
  2. 使用类型断言谨慎

    typescript
    // 避免
    const element = document.getElementById('root') as HTMLDivElement;
    
    // 更好的方式
    const element = document.getElementById('root');
    if (element instanceof HTMLDivElement) {
      // 这里 element 被推断为 HTMLDivElement
    }
  3. 使用 as const 断言

    typescript
    const colors = ['red', 'green', 'blue'] as const;
    type Color = typeof colors[number]; // 'red' | 'green' | 'blue'
  4. 使用 React.ReactNode 作为子元素类型

    typescript
    interface Props {
      children: React.ReactNode;
    }

17. TypeScript 与 Node.js 如何集成?

Details

TypeScript 与 Node.js 可以很好地集成,提供类型安全和更好的开发体验。以下是 TypeScript 与 Node.js 集成的主要方式:

1. 项目初始化

创建 TypeScript Node.js 项目:

bash
# 创建目录
mkdir my-node-app
cd my-node-app

# 初始化 package.json
npm init -y

# 安装 TypeScript
npm install --save-dev typescript @types/node ts-node

# 创建 tsconfig.json
npx tsc --init

2. 配置 tsconfig.json

json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "CommonJS",
    "moduleResolution": "node",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

3. 创建源代码文件

src/index.ts

typescript
import http from 'http';
import express, { Request, Response } from 'express';

const app = express();
const port = 3000;

app.get('/', (req: Request, res: Response) => {
  res.send('Hello TypeScript with Node.js!');
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

4. 安装依赖

bash
# 安装 Express
npm install express
npm install --save-dev @types/express

5. 运行脚本

在 package.json 中添加脚本:

json
{
  "scripts": {
    "start": "node dist/index.js",
    "build": "tsc",
    "dev": "ts-node src/index.ts"
  }
}

运行开发服务器:

bash
npm run dev

构建并运行:

bash
npm run build
npm start

6. 类型定义

自定义类型

typescript
// src/types.ts
export interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

export interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

服务层类型

typescript
// src/services/userService.ts
import { User } from '../types';

class UserService {
  private users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com', createdAt: new Date() },
    { id: 2, name: 'Bob', email: 'bob@example.com', createdAt: new Date() }
  ];

  async getUsers(): Promise<User[]> {
    return this.users;
  }

  async getUserById(id: number): Promise<User | undefined> {
    return this.users.find(user => user.id === id);
  }

  async createUser(user: Omit<User, 'id' | 'createdAt'>): Promise<User> {
    const newUser: User = {
      ...user,
      id: this.users.length + 1,
      createdAt: new Date()
    };
    this.users.push(newUser);
    return newUser;
  }
}

export default new UserService();

7. 中间件类型

typescript
// src/middleware/logger.ts
import { Request, Response, NextFunction } from 'express';

export const logger = (req: Request, res: Response, next: NextFunction) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
};

export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
  console.error(err);
  res.status(500).json({ error: 'Internal Server Error' });
};

8. 路由类型

typescript
// src/routes/userRoutes.ts
import express, { Router, Request, Response } from 'express';
import userService from '../services/userService';
import { User } from '../types';

const router: Router = express.Router();

router.get('/', async (req: Request, res: Response) => {
  try {
    const users = await userService.getUsers();
    res.json(users);
  } catch (error) {
    res.status(500).json({ error: 'Failed to get users' });
  }
});

router.get('/:id', async (req: Request, res: Response) => {
  try {
    const id = parseInt(req.params.id);
    const user = await userService.getUserById(id);
    if (user) {
      res.json(user);
    } else {
      res.status(404).json({ error: 'User not found' });
    }
  } catch (error) {
    res.status(500).json({ error: 'Failed to get user' });
  }
});

router.post('/', async (req: Request, res: Response) => {
  try {
    const userData: Omit<User, 'id' | 'createdAt'> = req.body;
    const newUser = await userService.createUser(userData);
    res.status(201).json(newUser);
  } catch (error) {
    res.status(500).json({ error: 'Failed to create user' });
  }
});

export default router;

9. 最佳实践

  1. 使用环境变量类型

    typescript
    // src/config.ts
    import dotenv from 'dotenv';
    
    dotenv.config();
    
    interface Config {
      port: number;
      databaseUrl: string;
      jwtSecret: string;
    }
    
    export const config: Config = {
      port: parseInt(process.env.PORT || '3000'),
      databaseUrl: process.env.DATABASE_URL || '',
      jwtSecret: process.env.JWT_SECRET || ''
    };
  2. 使用类型保护

    typescript
    function isUser(obj: any): obj is User {
      return obj && typeof obj === 'object' && 'id' in obj && 'name' in obj && 'email' in obj;
    }
  3. 使用泛型工具类型

    typescript
    // 从 User 类型中排除某些属性
    type UserWithoutId = Omit<User, 'id'>;
    
    // 从 User 类型中选择某些属性
    type UserBasicInfo = Pick<User, 'name' | 'email'>;
    
    // 使 User 类型的所有属性变为可选
    type UserPartial = Partial<User>;
    
    // 使 User 类型的所有属性变为必需
    type UserRequired = Required<User>;
  4. 使用 ts-node-dev 进行开发

    bash
    npm install --save-dev ts-node-dev

    在 package.json 中添加脚本:

    json
    {
      "scripts": {
        "dev": "ts-node-dev src/index.ts"
      }
    }

18. TypeScript 的最佳实践有哪些?

Details

TypeScript 的最佳实践可以帮助你编写更清晰、更可维护、更类型安全的代码。以下是一些 TypeScript 的最佳实践:

1. 类型定义

使用接口和类型别名

  • 接口:用于定义对象的结构和类型,适合用于类的实现和接口的扩展。
  • 类型别名:用于定义复杂类型,如联合类型、交叉类型、元组类型等。
typescript
// 接口
interface User {
  id: string;
  name: string;
  email: string;
}

// 类型别名
type UserId = string | number;
type UserWithOptionalEmail = Partial<User>;
type UserRequired = Required<User>;

避免使用 any 类型

any 类型会禁用 TypeScript 的类型检查,应尽量避免使用。如果确实需要,可以考虑使用 unknown 类型。

typescript
// 避免
let data: any = fetchData();

// 更好的方式
let data: unknown = fetchData();
if (typeof data === 'string') {
  // 这里 data 被推断为 string 类型
}

使用字面量类型

字面量类型可以使代码更精确,减少错误。

typescript
// 字符串字面量类型
type Direction = 'up' | 'down' | 'left' | 'right';

// 数字字面量类型
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

// 布尔字面量类型
type BooleanLiteral = true | false;

2. 代码组织

使用模块

使用 ES6 模块语法(importexport)组织代码,避免使用命名空间。

typescript
// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

// app.ts
import { add, subtract } from './math';

console.log(add(1, 2)); // 3
console.log(subtract(3, 1)); // 2

分离类型定义

将类型定义分离到单独的文件中,提高代码的可维护性。

typescript
// types.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

export interface Product {
  id: string;
  name: string;
  price: number;
}

// userService.ts
import { User } from './types';

class UserService {
  // 实现...
}

3. 类型安全

使用类型守卫

类型守卫可以帮助 TypeScript 推断更准确的类型,提高代码的类型安全。

typescript
interface Cat {
  name: string;
  meow(): void;
}

interface Dog {
  name: string;
  bark(): void;
}

// 自定义类型守卫
function isCat(pet: Cat | Dog): pet is Cat {
  return (pet as Cat).meow !== undefined;
}

function processPet(pet: Cat | Dog) {
  if (isCat(pet)) {
    // 这里 pet 被推断为 Cat 类型
    pet.meow();
  } else {
    // 这里 pet 被推断为 Dog 类型
    pet.bark();
  }
}

使用泛型

泛型可以使代码更加灵活和可复用,同时保持类型安全。

typescript
function identity<T>(arg: T): T {
  return arg;
}

function getFirstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

interface Container<T> {
  value: T;
  getValue(): T;
}

使用严格模式

tsconfig.json 中启用严格模式,提高类型安全。

json
{
  "compilerOptions": {
    "strict": true
  }
}

4. 性能优化

使用 const enum

const enum 会在编译时内联枚举值,提高性能。

typescript
const enum Direction {
  Up,
  Down,
  Left,
  Right
}

let direction = Direction.Up; // 编译为:let direction = 0;

使用 as const 断言

as const 断言可以使类型更加精确,避免不必要的类型检查。

typescript
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'

const user = {
  id: 1,
  name: 'Alice'
} as const;
// user 的类型是 { readonly id: 1; readonly name: 'Alice' }

避免过度使用类型断言

过度使用类型断言会降低类型安全,应尽量避免。

typescript
// 避免
const element = document.getElementById('root') as HTMLDivElement;

// 更好的方式
const element = document.getElementById('root');
if (element instanceof HTMLDivElement) {
  // 这里 element 被推断为 HTMLDivElement
}

5. 工具和配置

使用 ESLint

使用 ESLint 检查 TypeScript 代码,提高代码质量。

bash
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

创建 .eslintrc.js 文件:

javascript
module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended'
  ],
  rules: {
    // 自定义规则
  }
};

使用 Prettier

使用 Prettier 格式化 TypeScript 代码,保持代码风格一致。

bash
npm install --save-dev prettier

创建 .prettierrc 文件:

json
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2
}

合理配置 tsconfig.json

根据项目需求合理配置 tsconfig.json,提高编译效率。

json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "CommonJS",
    "moduleResolution": "node",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "sourceMap": true,
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

6. 代码可读性

使用有意义的类型名称

使用有意义的类型名称,提高代码的可读性。

typescript
// 好的命名
type UserId = string | number;
type EmailAddress = string;
type DateString = string;

// 不好的命名
type T = string | number;
type S = string;
type D = string;

使用 JSDoc 注释

使用 JSDoc 注释为类型和函数添加文档,提高代码的可读性。

typescript
/**
 * 用户接口
 */
interface User {
  /**
   * 用户 ID
   */
  id: string;
  /**
   * 用户名称
   */
  name: string;
  /**
   * 用户邮箱
   */
  email: string;
}

/**
 * 加法函数
 * @param a 第一个加数
 * @param b 第二个加数
 * @returns 两个数的和
 */
function add(a: number, b: number): number {
  return a + b;
}

使用类型推断

对于简单的变量和函数,可以利用类型推断,减少冗余的类型标注。

typescript
// 利用类型推断
let x = 10; // 推断为 number 类型
let y = "hello"; // 推断为 string 类型

// 显式标注复杂类型
interface User {
  id: string;
  name: string;
  email: string;
}

let user: User = { id: "1", name: "Alice", email: "alice@example.com" };

7. 常见陷阱

避免循环依赖

循环依赖会导致编译错误和运行时问题,应尽量避免。

避免过度工程化

不要过度使用 TypeScript 的高级特性,保持代码简洁明了。

注意类型的边界情况

在定义类型时,要考虑边界情况,确保类型的完整性和准确性。

定期更新类型定义

定期更新第三方库的类型定义,确保类型的准确性。

8. 总结

TypeScript 的最佳实践包括:

  • 合理使用接口和类型别名
  • 避免使用 any 类型
  • 使用字面量类型提高类型精确性
  • 使用模块组织代码
  • 分离类型定义
  • 使用类型守卫提高类型安全
  • 使用泛型提高代码复用性
  • 启用严格模式
  • 使用 const enumas const 提高性能
  • 避免过度使用类型断言
  • 使用 ESLint 和 Prettier 提高代码质量
  • 合理配置 tsconfig.json
  • 使用有意义的类型名称
  • 使用 JSDoc 注释
  • 利用类型推断
  • 避免循环依赖
  • 避免过度工程化
  • 注意类型的边界情况
  • 定期更新类型定义

通过遵循这些最佳实践,你可以编写更清晰、更可维护、更类型安全的 TypeScript 代码。

19. TypeScript 的性能优化有哪些?

Details

TypeScript 的性能优化可以提高编译速度和运行时性能,以下是一些 TypeScript 的性能优化策略:

1. 编译性能优化

1. 合理配置 tsconfig.json

  • 增量编译:启用增量编译,只重新编译修改的文件。

    json
    {
      "compilerOptions": {
        "incremental": true,
        "tsBuildInfoFile": "./dist/.tsbuildinfo"
      }
    }
  • 跳过类型检查:对于大型项目,可以跳过某些文件的类型检查。

    json
    {
      "compilerOptions": {
        "skipLibCheck": true,
        "skipDefaultLibCheck": true
      }
    }
  • 限制编译范围:使用 includeexclude 限制编译范围。

    json
    {
      "include": ["src/**/*"],
      "exclude": ["node_modules", "dist", "**/*.test.ts"]
    }
  • 使用 composite 项目:对于大型项目,使用 composite 项目提高编译速度。

    json
    {
      "compilerOptions": {
        "composite": true
      }
    }

2. 使用构建工具

  • 使用 esbuild:esbuild 是一个快速的 JavaScript/TypeScript 构建工具,可以显著提高编译速度。

    bash
    npm install esbuild
  • 使用 SWC:SWC 是一个快速的 TypeScript/JavaScript 编译器,可以替代 Babel。

    bash
    npm install @swc/core @swc/cli
  • 使用 Webpack 或 Vite:使用现代构建工具,它们通常有更好的缓存机制和并行编译能力。

3. 代码组织

  • 拆分大型文件:将大型文件拆分为多个小文件,提高编译速度。
  • 避免循环依赖:循环依赖会增加编译时间,应尽量避免。
  • 使用模块联邦:对于大型项目,使用模块联邦可以减少编译时间。

2. 运行时性能优化

1. 类型相关优化

  • 使用 const enumconst enum 会在编译时内联枚举值,减少运行时开销。

    typescript
    const enum Direction {
      Up,
      Down,
      Left,
      Right
    }
  • 使用 as const 断言as const 断言可以使类型更加精确,避免不必要的类型检查。

    typescript
    const colors = ['red', 'green', 'blue'] as const;
    type Color = typeof colors[number];
  • 避免过度使用泛型:过度使用泛型会增加运行时开销,应合理使用。

  • 避免使用 any 类型any 类型会禁用类型检查,可能导致运行时错误。

2. 代码优化

  • 使用 MapSet:对于频繁查找的场景,使用 MapSet 可以提高性能。

    typescript
    // 使用 Map 存储键值对
    const userMap = new Map<string, User>();
    userMap.set('1', { id: '1', name: 'Alice' });
    
    // 使用 Set 存储唯一值
    const uniqueIds = new Set<string>();
    uniqueIds.add('1');
  • 使用 for...of 循环:对于数组和可迭代对象,for...of 循环通常比 for...in 循环更快。

    typescript
    // 快
    for (const item of array) {
      // 处理 item
    }
    
    // 慢
    for (const index in array) {
      const item = array[index];
      // 处理 item
    }
  • 使用 Object.freeze:对于不需要修改的对象,使用 Object.freeze 可以提高性能。

    typescript
    const config = Object.freeze({
      apiUrl: 'https://api.example.com',
      timeout: 10000
    });
  • 使用 Array.fromArray.of:对于数组操作,使用 Array.fromArray.of 可以提高性能。

    typescript
    // 使用 Array.from 创建数组
    const array = Array.from({ length: 10 }, (_, i) => i);
    
    // 使用 Array.of 创建数组
    const array = Array.of(1, 2, 3, 4, 5);

3. 内存优化

  • 避免内存泄漏:及时清理定时器、事件监听器和引用,避免内存泄漏。

    typescript
    // 清理定时器
    const timer = setInterval(() => {
      // 处理逻辑
    }, 1000);
    clearInterval(timer);
    
    // 清理事件监听器
    const element = document.getElementById('button');
    const handleClick = () => {
      // 处理点击事件
    };
    element?.addEventListener('click', handleClick);
    element?.removeEventListener('click', handleClick);
  • 使用 WeakMapWeakSet:对于临时引用,使用 WeakMapWeakSet 可以避免内存泄漏。

    typescript
    // 使用 WeakMap 存储对象相关数据
    const weakMap = new WeakMap<object, any>();
    const obj = {};
    weakMap.set(obj, 'data');
    
    // 使用 WeakSet 存储对象
    const weakSet = new WeakSet<object>();
    weakSet.add(obj);
  • 避免创建不必要的对象:尽量复用对象,避免创建不必要的对象。

    typescript
    // 避免
    function processData(data: any[]) {
      return data.map(item => {
        return { ...item, processed: true }; // 每次都创建新对象
      });
    }
    
    // 更好的方式
    function processData(data: any[]) {
      for (const item of data) {
        item.processed = true; // 直接修改原对象
      }
      return data;
    }

3. 开发工具优化

1. 使用 TypeScript 语言服务插件

  • 使用 typescript-svelte-plugin:对于 Svelte 项目,使用 typescript-svelte-plugin 提高类型检查速度。
  • 使用 typescript-react-plugin:对于 React 项目,使用 typescript-react-plugin 提高类型检查速度。

2. 编辑器优化

  • 使用 VS Code:VS Code 对 TypeScript 有很好的支持,可以提高开发效率。
  • 启用快速文件切换:在 VS Code 中启用快速文件切换,可以更快地导航代码。
  • 使用 TypeScript 语言服务:确保 VS Code 使用项目本地的 TypeScript 版本,避免版本冲突。

3. 构建流程优化

  • 使用缓存:在 CI/CD 流程中使用缓存,避免重复安装依赖和编译。
  • 并行构建:对于大型项目,使用并行构建可以提高构建速度。
  • 增量构建:使用增量构建,只重新构建修改的文件。

4. 性能监控

1. 使用性能分析工具

  • 使用 Chrome DevTools:使用 Chrome DevTools 的 Performance 面板分析运行时性能。

  • 使用 console.time:使用 console.timeconsole.timeEnd 测量代码执行时间。

    typescript
    console.time('processData');
    // 处理数据
    console.timeEnd('processData');
  • 使用 performance.now():使用 performance.now() 测量更精确的时间。

    typescript
    const start = performance.now();
    // 处理数据
    const end = performance.now();
    console.log(`Processed in ${end - start}ms`);

2. 监控编译时间

  • 使用 tsc --extendedDiagnostics:使用 tsc --extendedDiagnostics 查看编译时间和内存使用情况。

    bash
    npx tsc --extendedDiagnostics
  • 使用 speed-measure-webpack-plugin:对于 Webpack 项目,使用 speed-measure-webpack-plugin 分析构建时间。

    bash
    npm install --save-dev speed-measure-webpack-plugin

5. 最佳实践

  1. 合理使用类型:使用适当的类型,避免过度使用复杂类型。
  2. 优化导入:使用 import 语句的具体导入,避免导入整个模块。
  3. 使用 readonly:对于不需要修改的属性,使用 readonly 关键字。
  4. 使用 as const:对于常量值,使用 as const 断言。
  5. 避免使用 evaleval 会降低性能,应尽量避免。
  6. 使用 Object.keysObject.values:对于对象操作,使用 Object.keysObject.values 可以提高性能。
  7. 使用 Promise.all:对于并行操作,使用 Promise.all 可以提高性能。
  8. 使用 async/await:对于异步操作,使用 async/await 可以提高代码可读性和性能。
  9. 避免使用 try/catchtry/catch 会降低性能,应尽量避免在热路径中使用。
  10. 使用 Web Workers:对于密集计算,使用 Web Workers 可以提高性能。

6. 总结

TypeScript 的性能优化包括:

  • 编译性能优化:合理配置 tsconfig.json、使用构建工具、优化代码组织
  • 运行时性能优化:类型相关优化、代码优化、内存优化
  • 开发工具优化:使用 TypeScript 语言服务插件、编辑器优化、构建流程优化
  • 性能监控:使用性能分析工具、监控编译时间
  • 最佳实践:合理使用类型、优化导入、使用 readonlyas const

通过这些优化策略,可以提高 TypeScript 项目的编译速度和运行时性能,改善开发体验和用户体验。

20. TypeScript 的常见错误和解决方案

Details

TypeScript 中常见的错误类型和解决方案如下:

1. 类型错误

1.1 类型不匹配

错误示例

typescript
let num: number = "123"; // 类型 'string' 不能赋值给类型 'number'

解决方案

  • 确保类型匹配
  • 使用类型转换
    typescript
    let num: number = Number("123");

1.2 缺少属性

错误示例

typescript
interface User {
  id: string;
  name: string;
  email: string;
}

const user: User = { id: "1", name: "Alice" }; // 属性 'email' 缺失

解决方案

  • 提供所有必需的属性
  • 使用可选属性
    typescript
    interface User {
      id: string;
      name: string;
      email?: string; // 可选属性
    }

1.3 类型断言错误

错误示例

typescript
const element = document.getElementById('root') as HTMLInputElement;
element.value = "test"; // 如果元素不是 input,运行时会出错

解决方案

  • 使用类型守卫
    typescript
    const element = document.getElementById('root');
    if (element instanceof HTMLInputElement) {
      element.value = "test";
    }

2. 编译错误

2.1 模块解析错误

错误示例

typescript
import { add } from './math'; // 找不到模块 './math'

解决方案

  • 确保文件路径正确
  • 确保文件存在
  • 检查 tsconfig.json 中的模块解析配置
    json
    {
      "compilerOptions": {
        "moduleResolution": "node"
      }
    }

2.2 循环依赖

错误示例

typescript
// a.ts
import { b } from './b';
export const a = b + 1;

// b.ts
import { a } from './a';
export const b = a + 1;

解决方案

  • 重构代码,避免循环依赖
  • 将共享代码提取到单独的文件

2.3 类型定义缺失

错误示例

typescript
import * as _ from 'lodash'; // 找不到模块 'lodash' 的类型定义

解决方案

  • 安装类型定义文件
    bash
    npm install --save-dev @types/lodash
  • 创建自定义类型定义

3. 运行时错误

3.1 空值错误

错误示例

typescript
const user: User | null = null;
console.log(user.name); // 运行时错误:Cannot read property 'name' of null

解决方案

  • 使用类型守卫
    typescript
    if (user !== null) {
      console.log(user.name);
    }
  • 使用可选链操作符
    typescript
    console.log(user?.name);

3.2 类型断言错误

错误示例

typescript
const data: any = "string";
const num: number = data as number;
console.log(num.toFixed(2)); // 运行时错误:num.toFixed is not a function

解决方案

  • 使用类型守卫
    typescript
    if (typeof data === 'number') {
      console.log(data.toFixed(2));
    }
  • 避免使用 any 类型

3.3 数组越界

错误示例

typescript
const array: number[] = [1, 2, 3];
console.log(array[5]); // 运行时错误:undefined

解决方案

  • 检查数组长度
    typescript
    if (array.length > 5) {
      console.log(array[5]);
    }
  • 使用可选链操作符
    typescript
    console.log(array[5]?.toString());

4. 配置错误

4.1 tsconfig.json 配置错误

错误示例

json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "ES6",
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

解决方案

  • 确保配置项正确
  • 检查目标环境的兼容性

4.2 路径别名配置错误

错误示例

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

解决方案

  • 确保路径别名配置正确
  • 确保构建工具支持路径别名

5. 最佳实践

  1. 使用严格模式:在 tsconfig.json 中启用严格模式,捕获更多类型错误。

    json
    {
      "compilerOptions": {
        "strict": true
      }
    }
  2. 使用类型守卫:使用类型守卫处理联合类型和可能的空值。

  3. 使用可选链和空值合并:使用可选链 (?.) 和空值合并 (??) 操作符处理可能的空值。

    typescript
    const name = user?.name ?? "Unknown";
  4. 使用 unknown 替代 any:使用 unknown 类型代替 any,提高类型安全。

  5. 使用泛型:使用泛型提高代码复用性和类型安全。

  6. 使用 as const:使用 as const 断言提高类型精确性。

  7. 使用 JSDoc 注释:使用 JSDoc 注释为类型和函数添加文档。

  8. 使用 ESLint:使用 ESLint 检查 TypeScript 代码,捕获更多错误。

  9. 使用 Prettier:使用 Prettier 格式化 TypeScript 代码,保持代码风格一致。

  10. 定期更新依赖:定期更新 TypeScript 和相关依赖,获取最新的类型定义和性能改进。

6. 总结

TypeScript 的常见错误包括:

  • 类型错误:类型不匹配、缺少属性、类型断言错误
  • 编译错误:模块解析错误、循环依赖、类型定义缺失
  • 运行时错误:空值错误、类型断言错误、数组越界
  • 配置错误:tsconfig.json 配置错误、路径别名配置错误

通过遵循最佳实践,可以减少这些错误的发生:

  • 使用严格模式
  • 使用类型守卫
  • 使用可选链和空值合并
  • 使用 unknown 替代 any
  • 使用泛型
  • 使用 as const
  • 使用 JSDoc 注释
  • 使用 ESLint
  • 使用 Prettier
  • 定期更新依赖

这些策略可以帮助你编写更类型安全、更可维护的 TypeScript 代码。

21. 请简述 TypeScript 中 interface 和 type 的差别

Details

在 TypeScript 中,interfacetype 都用于定义类型,但它们有一些重要的区别和使用场景。以下是它们之间的主要差异:

1. 定义方式

  • interface

    • 用于定义对象的结构,可以声明方法、属性等。
    • 支持扩展(通过继承)和合并(同名的接口会自动合并)。
    typescript
    interface Person {
        name: string;
        age: number;
        greet(): void;
    }
  • type

    • 用于定义任意类型,可以是基本类型、联合类型、元组等。
    • 不能像接口那样自动合并。
    typescript
    type Person = {
        name: string;
        age: number;
        greet: () => void;
    };

2. 扩展能力

  • interface

    • 可以通过继承(extends)来扩展其他接口。
    • 允许多个接口合并。
    typescript
    interface Employee extends Person {
        employeeId: number;
    }
    
    interface Person {
        name: string;
        age: number;
    }
  • type

    • 可以通过交叉类型(&)来组合多个类型。
    typescript
    type Employee = Person & {
        employeeId: number;
    };

3. 支持的类型

  • interface

    • 主要用于定义对象的结构,不能直接表示基本类型的联合。
    typescript
    interface A {}
    interface B {}
    // 不能这样定义联合类型
    // interface C = A | B; // 这是错误的
  • type

    • 可以定义基本类型的联合、交叉、元组等。
    typescript
    type StringOrNumber = string | number; // 联合类型

4. 语法和语义

  • interface

    • 更适合描述对象的形状(结构),通常用于构建面向对象的编程风格。
  • type

    • 更灵活,适用于任何类型的定义。

5. 性能与使用建议

在性能方面,interfacetype 的差异通常不明显,但在大型项目中,使用 interface 可以更好地利用 TypeScript 的类型合并特性。同时,interface 也更适合用于库和框架的 API 定义,因为它的合并特性使得扩展和修改变得更简单。

总结

  • 选择 interface:当你需要定义一个对象的结构,并且可能会有多个同名接口合并时,使用 interface 更为合适。
  • 选择 type:当你需要定义复杂的类型(如联合、交叉类型)时,或者不需要合并时,使用 type 更加灵活。