TypeScript 面试题目
1. 什么是 TypeScript?它与 JavaScript 有什么关系?
Details
TypeScript 是 JavaScript 的超集,它扩展了 JavaScript 的功能,添加了静态类型检查和其他特性。TypeScript 最终会被编译成 JavaScript,因此可以在任何支持 JavaScript 的环境中运行。
TypeScript 与 JavaScript 的关系
- 超集关系:TypeScript 包含了 JavaScript 的所有功能,并且添加了额外的特性。
- 静态类型:TypeScript 引入了静态类型系统,可以在编译时捕获类型错误。
- 编译过程:TypeScript 代码需要通过 TypeScript 编译器(tsc)编译成 JavaScript 代码才能运行。
- 向后兼容:所有有效的 JavaScript 代码都是有效的 TypeScript 代码,因此可以逐步将 JavaScript 项目迁移到 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 - 接口:定义对象的结构和类型
示例代码
// 原始类型
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 中用于定义对象结构和类型的一种方式,它可以描述对象的形状、方法和属性。
接口的基本语法
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}`);
}
};接口与类型别名的区别
声明合并:接口可以多次声明,会自动合并;类型别名不能多次声明。
typescript// 接口可以合并 interface Person { name: string; } interface Person { age: number; } // 等同于 interface Person { name: string; age: number; } // 类型别名不能合并 type Person = { name: string; }; // 错误:重复声明类型别名 type Person = { age: number; };扩展方式:接口通过
extends扩展;类型别名通过交叉类型&扩展。typescript// 接口扩展 interface Animal { name: string; } interface Dog extends Animal { breed: string; } // 类型别名扩展 type Animal = { name: string; }; type Dog = Animal & { breed: string; };实现方式:类可以实现接口;类不能实现类型别名(除非类型别名是对象类型)。
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. 什么是泛型(Generics)?它的作用是什么?
Details
泛型(Generics)是 TypeScript 中一种强大的特性,允许在定义函数、接口或类时使用类型参数,使代码更加灵活和可复用。
泛型的基本语法
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>("hello");
let output2 = identity<number>(10);
// 类型推断
let output3 = identity("world"); // 自动推断为 string 类型泛型的作用
- 类型安全:在编译时检查类型,减少运行时错误
- 代码复用:编写通用的代码,适用于多种类型
- 灵活性:允许用户指定类型,而不是硬编码
- 可读性:使代码更加清晰,明确表达类型意图
泛型的高级用法
泛型接口:
typescriptinterface 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");泛型约束:
typescriptinterface 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);泛型默认类型:
typescriptfunction 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 中一种告诉编译器某个值的具体类型的方式,它不会在运行时做任何类型转换,只是在编译时告诉编译器如何处理类型。
类型断言的两种方式
尖括号语法:
typescriptlet someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;as 语法:
typescriptlet someValue: any = "this is a string"; let strLength: number = (someValue as string).length;注意:在 JSX 中,只能使用
as语法,因为尖括号会被解析为 JSX 元素。
类型断言的使用场景
从 any 类型转换:当你知道一个 any 类型的变量的具体类型时,可以使用类型断言。
typescriptlet userInput: any = "123"; let numericInput: number = (userInput as string).length;从联合类型转换:当你知道联合类型中的具体类型时,可以使用类型断言。
typescriptinterface 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(); } }从父类型转换为子类型:当你知道一个变量的实际类型是子类型时,可以使用类型断言。
typescriptclass Animal { name: string; } class Dog extends Animal { bark(): void { console.log("Woof!"); } } let animal: Animal = new Dog(); (animal as Dog).bark();
类型断言的注意事项
- 类型断言不是类型转换:它不会在运行时改变变量的类型,只是在编译时告诉编译器如何处理类型。
- 类型断言要谨慎使用:如果断言的类型与实际类型不符,可能会导致运行时错误。
- 优先使用类型守卫:对于联合类型,优先使用类型守卫(如
typeof、instanceof、自定义类型守卫),而不是类型断言。
6. TypeScript 中的枚举(Enum)是什么?有什么特点?
Details
枚举(Enum)是 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"
}枚举的特点
默认值:数字枚举的默认值从 0 开始,依次递增。
typescriptenum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3 }自定义值:可以为枚举成员指定自定义值。
typescriptenum Direction { Up = 1, Down, Left, Right } // Up: 1, Down: 2, Left: 3, Right: 4反向映射:数字枚举支持反向映射,可以通过值获取枚举成员的名称。
typescriptenum Direction { Up, Down, Left, Right } console.log(Direction[0]); // 输出:"Up" console.log(Direction[1]); // 输出:"Down"常量枚举:使用
const enum可以在编译时内联枚举值,提高性能。typescriptconst enum Direction { Up, Down, Left, Right } let direction = Direction.Up; // 编译为:let direction = 0;外部枚举:使用
declare enum可以声明一个枚举,但不生成代码,用于描述外部库中的枚举。typescriptdeclare enum Direction { Up, Down, Left, Right }
枚举的使用场景
- 状态管理:定义应用中的各种状态
- 错误码:定义错误类型和错误码
- 配置选项:定义配置项的可能值
- 方向、颜色等常量:定义一组相关的常量
枚举的优缺点
优点:
- 代码更加清晰,易于理解
- 提供了类型安全
- 支持反向映射(数字枚举)
缺点:
- 会生成额外的代码(非 const enum)
- 字符串枚举不支持反向映射
- 枚举成员的值是固定的,不能动态修改
7. TypeScript 中的装饰器(Decorator)是什么?有哪些类型?
Details
装饰器(Decorator)是 TypeScript 中一种特殊的语法,用于修改类、方法、属性或参数的行为。装饰器是一种元编程特性,允许在不修改原始代码的情况下增强类和类成员。
装饰器的基本语法
装饰器使用 @ 符号后跟装饰器名称的语法:
@decorator
class MyClass {
@propertyDecorator
property: string;
@methodDecorator
method(@parameterDecorator param: string) {
// 方法体
}
}装饰器的类型
类装饰器:应用于类的声明
typescriptfunction 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; } }方法装饰器:应用于方法的声明
typescriptfunction 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; } }属性装饰器:应用于属性的声明
typescriptfunction 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; } }参数装饰器:应用于参数的声明
typescriptfunction 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; } }
装饰器的执行顺序
装饰器的执行顺序如下:
- 参数装饰器,然后是方法装饰器、访问器装饰器、属性装饰器
- 类装饰器
装饰器的应用场景
- 日志记录:记录方法的调用和参数
- 性能监控:测量方法的执行时间
- 权限控制:检查用户是否有权限执行某个方法
- 依赖注入:自动注入依赖
- 序列化/反序列化:自动处理对象的序列化和反序列化
装饰器的配置
要使用装饰器,需要在 tsconfig.json 中启用装饰器特性:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}8. TypeScript 中的模块(Module)和命名空间(Namespace)有什么区别?
Details
TypeScript 中的模块(Module)和命名空间(Namespace)都是用于组织代码的方式,但它们有一些重要的区别。
模块(Module)
模块是 TypeScript 中组织代码的主要方式,它使用 ES6 模块语法(import 和 export)。
模块的特点
- 文件级作用域:每个模块都有自己的作用域,模块内的声明不会污染全局作用域
- 显式导出:需要使用
export关键字导出模块成员 - 显式导入:需要使用
import关键字导入模块成员 - 支持静态分析:TypeScript 编译器可以分析模块依赖
- 支持树摇(Tree Shaking):未使用的模块成员会被移除
模块的示例
// 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 关键字定义。
命名空间的特点
- 全局作用域:命名空间是全局作用域的一部分
- 隐式导出:命名空间内的声明默认是可见的
- 使用点语法访问:使用
NamespaceName.MemberName的方式访问成员 - 支持合并:同名命名空间会自动合并
- 支持嵌套:可以在命名空间内定义子命名空间
命名空间的示例
// 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 项目中,推荐使用模块而不是命名空间,因为:
- 模块与 ES6 模块标准兼容
- 模块支持树摇,减少打包体积
- 模块提供更好的代码组织和封装
- 模块是现代前端工具链(如 Webpack、Rollup)的标准选择
9. TypeScript 中的类型守卫(Type Guard)是什么?有哪些类型?
Details
类型守卫(Type Guard)是 TypeScript 中一种用于在运行时检查类型的机制,它可以帮助编译器在编译时确定变量的具体类型。
类型守卫的作用
类型守卫的主要作用是在运行时检查变量的类型,从而在编译时提供更准确的类型推断。这使得 TypeScript 能够在编译时捕获更多的类型错误,同时保持代码的灵活性。
类型守卫的类型
typeof 类型守卫:用于检查原始类型
typescriptfunction processValue(value: string | number) { if (typeof value === "string") { // 这里 value 被推断为 string 类型 return value.toUpperCase(); } else { // 这里 value 被推断为 number 类型 return value.toFixed(2); } }instanceof 类型守卫:用于检查对象是否是某个类的实例
typescriptclass 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(); } }in 类型守卫:用于检查对象是否具有某个属性
typescriptinterface 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(); } }自定义类型守卫:使用
is关键字定义自定义类型守卫typescriptinterface 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(); } }字面量类型守卫:用于检查字符串或数字字面量类型
typescripttype 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!"); } }
类型守卫的应用场景
- 联合类型处理:当变量是联合类型时,使用类型守卫可以确定其具体类型
- 类型安全:在运行时检查类型,确保代码的类型安全
- 代码可读性:使代码更加清晰,明确表达类型检查的意图
- 减少类型断言:减少使用类型断言的需要,使代码更加类型安全
10. TypeScript 中的高级类型有哪些?
Details
TypeScript 提供了多种高级类型,用于处理复杂的类型场景。以下是一些常用的高级类型:
1. 交叉类型(Intersection Types)
交叉类型将多个类型合并为一个类型,新类型包含所有类型的特性。
interface A {
a: number;
}
interface B {
b: string;
}
type C = A & B;
let c: C = {
a: 1,
b: "test"
};2. 联合类型(Union Types)
联合类型表示一个值可以是多个类型中的一个。
type StringOrNumber = string | number;
let value: StringOrNumber = "test";
value = 10;3. 类型守卫(Type Guards)
类型守卫用于在运行时检查类型,详见前面的问题。
4. 类型别名(Type Aliases)
类型别名为类型创建新名称,使代码更加清晰。
type UserId = string | number;
type Direction = "up" | "down" | "left" | "right";5. 字符串字面量类型(String Literal Types)
字符串字面量类型表示具体的字符串值。
type Color = "red" | "green" | "blue";
let color: Color = "red";
// 错误:"yellow" 不是有效的 Color 类型
// color = "yellow";6. 数字字面量类型(Number Literal Types)
数字字面量类型表示具体的数字值。
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 3;
// 错误:7 不是有效的 DiceRoll 类型
// roll = 7;7. 映射类型(Mapped Types)
映射类型用于基于现有类型创建新类型。
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)
条件类型根据条件选择类型。
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>; // number9. 推断类型(Infer Types)
推断类型用于从类型中提取类型信息。
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>; // number10. 模板字面量类型(Template Literal Types)
模板字面量类型用于创建基于字符串模板的类型。
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)
递归类型用于定义自引用的类型。
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)
索引类型用于访问对象的属性类型。
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"); // number11. TypeScript 中的 any、unknown、never 和 void 类型有什么区别?
Details
TypeScript 中有几种特殊类型,它们各有不同的用途和特点:
1. any 类型
any 类型表示任意类型,它会禁用 TypeScript 的类型检查。
特点
- 可以赋值给任何类型
- 任何类型可以赋值给
any - 可以访问任何属性和方法,不会有类型检查
- 编译时不会报错,运行时可能会出错
使用场景
- 当你不确定变量的类型时
- 当你想禁用类型检查时
- 当你与 JavaScript 代码交互时
let anyValue: any = "hello";
anyValue = 10; // 可以
anyValue = true; // 可以
anyValue.foo(); // 编译时不会报错,运行时可能会出错2. unknown 类型
unknown 类型表示未知类型,它是 TypeScript 3.0 引入的类型。
特点
- 任何类型可以赋值给
unknown unknown不能赋值给其他类型(除非使用类型断言)- 不能直接访问
unknown类型的属性和方法 - 需要类型检查或类型断言才能使用
使用场景
- 当你不确定变量的类型时
- 当你想进行类型检查时
- 当你想替代
any类型时
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类型可以赋值给任何类型- 通常用于表示函数永远不会返回
- 用于类型收窄
使用场景
- 函数抛出异常
- 函数无限循环
- 类型收窄的最终情况
// 函数抛出异常
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 - 可以赋值
undefined和null(在strictNullChecks为 false 时) - 通常用于函数返回类型
使用场景
- 函数没有返回值
- 函数返回
undefined
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 | 无返回值 | any、unknown | undefined、null | 函数没有返回值 |
12. TypeScript 中的配置文件 tsconfig.json 有哪些重要配置项?
Details
tsconfig.json 是 TypeScript 项目的配置文件,用于指定编译选项和项目设置。以下是一些重要的配置项:
1. 编译选项(compilerOptions)
目标版本(target)
指定编译后的 JavaScript 版本。
{
"compilerOptions": {
"target": "ES5" // 可选值:ES3, ES5, ES6/ES2015, ES7/ES2016, ES8/ES2017, ES9/ES2018, ES10/ES2019, ES2020, ES2021, ES2022, ESNext
}
}模块系统(module)
指定生成的模块系统。
{
"compilerOptions": {
"module": "CommonJS" // 可选值:CommonJS, AMD, UMD, System, ES2015, ES2020, ESNext, None
}
}模块解析策略(moduleResolution)
指定模块解析策略。
{
"compilerOptions": {
"moduleResolution": "node" // 可选值:node, classic
}
}严格模式(strict)
启用所有严格类型检查选项。
{
"compilerOptions": {
"strict": true // 等价于启用所有严格选项
}
}严格空检查(strictNullChecks)
启用严格的空值检查。
{
"compilerOptions": {
"strictNullChecks": true // 不允许 null 和 undefined 赋值给非空类型
}
}严格函数类型(strictFunctionTypes)
启用严格的函数类型检查。
{
"compilerOptions": {
"strictFunctionTypes": true // 函数参数类型逆变,返回值类型协变
}
}严格绑定检查(strictBindCallApply)
启用严格的 bind、call 和 apply 方法检查。
{
"compilerOptions": {
"strictBindCallApply": true // 检查 bind、call 和 apply 的参数类型
}
}严格属性初始化(strictPropertyInitialization)
启用严格的属性初始化检查。
{
"compilerOptions": {
"strictPropertyInitialization": true // 检查类属性是否在构造函数中初始化
}
}无隐式 any(noImplicitAny)
禁止隐式 any 类型。
{
"compilerOptions": {
"noImplicitAny": true // 不允许隐式 any 类型
}
}无隐式返回(noImplicitReturns)
禁止函数隐式返回。
{
"compilerOptions": {
"noImplicitReturns": true // 要求函数的所有路径都有返回值
}
}无隐式 this(noImplicitThis)
禁止隐式 this 类型。
{
"compilerOptions": {
"noImplicitThis": true // 不允许隐式 this 类型
}
}始终严格模式(alwaysStrict)
在编译后的 JavaScript 文件中添加 "use strict"。
{
"compilerOptions": {
"alwaysStrict": true // 在输出文件中添加 "use strict"
}
}源映射(sourceMap)
生成源映射文件,用于调试。
{
"compilerOptions": {
"sourceMap": true // 生成 .map 文件
}
}输出目录(outDir)
指定编译输出的目录。
{
"compilerOptions": {
"outDir": "./dist" // 编译输出到 dist 目录
}
}根目录(rootDir)
指定源代码的根目录。
{
"compilerOptions": {
"rootDir": "./src" // 源代码在 src 目录
}
}声明文件(declaration)
生成 .d.ts 声明文件。
{
"compilerOptions": {
"declaration": true // 生成声明文件
}
}声明文件目录(declarationDir)
指定声明文件的输出目录。
{
"compilerOptions": {
"declarationDir": "./types" // 声明文件输出到 types 目录
}
}模块目标(moduleTarget)
指定模块目标。
{
"compilerOptions": {
"moduleTarget": "es2015" // 模块目标版本
}
}实验性装饰器(experimentalDecorators)
启用实验性装饰器特性。
{
"compilerOptions": {
"experimentalDecorators": true // 启用装饰器
}
}装饰器元数据(emitDecoratorMetadata)
启用装饰器元数据。
{
"compilerOptions": {
"emitDecoratorMetadata": true // 启用装饰器元数据
}
}2. 包含和排除(include, exclude, files)
包含(include)
指定要包含的文件或目录。
{
"include": ["src/**/*"] // 包含 src 目录下的所有文件
}排除(exclude)
指定要排除的文件或目录。
{
"exclude": ["node_modules", "dist"] // 排除 node_modules 和 dist 目录
}文件(files)
指定要编译的具体文件。
{
"files": ["src/index.ts", "src/app.ts"] // 只编译指定的文件
}3. 继承(extends)
继承其他 tsconfig.json 文件。
{
"extends": "./tsconfig.base.json" // 继承基础配置
}4. 引用(references)
引用其他 TypeScript 项目。
{
"references": [
{ "path": "./../common" },
{ "path": "./../utils" }
]
}5. 示例配置
{
"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
显示帮助信息。
tsc --help--version 或 -v
显示 TypeScript 版本。
tsc --version--init
初始化一个新的 TypeScript 项目,生成 tsconfig.json 文件。
tsc --init--project 或 -p
指定 tsconfig.json 文件的路径。
tsc --project ./tsconfig.json--watch 或 -w
启用监视模式,当文件变化时自动重新编译。
tsc --watch2. 编译选项
--target 或 -t
指定编译目标 JavaScript 版本。
tsc --target ES5--module 或 -m
指定生成的模块系统。
tsc --module CommonJS--outDir 或 -d
指定输出目录。
tsc --outDir ./dist--outFile 或 -o
将所有输出文件合并为一个文件。
tsc --outFile ./dist/bundle.js--sourceMap 或 -s
生成源映射文件。
tsc --sourceMap--declaration 或 -d
生成 .d.ts 声明文件。
tsc --declaration--declarationDir
指定声明文件的输出目录。
tsc --declaration --declarationDir ./types--noEmit
只进行类型检查,不生成输出文件。
tsc --noEmit--noEmitOnError
如果有错误,不生成输出文件。
tsc --noEmitOnError--strict
启用所有严格类型检查选项。
tsc --strict--strictNullChecks
启用严格的空值检查。
tsc --strictNullChecks--noImplicitAny
禁止隐式 any 类型。
tsc --noImplicitAny--noImplicitReturns
禁止函数隐式返回。
tsc --noImplicitReturns--noImplicitThis
禁止隐式 this 类型。
tsc --noImplicitThis--alwaysStrict
在编译后的 JavaScript 文件中添加 "use strict"。
tsc --alwaysStrict--experimentalDecorators
启用实验性装饰器特性。
tsc --experimentalDecorators--emitDecoratorMetadata
启用装饰器元数据。
tsc --emitDecoratorMetadata3. 其他选项
--listFiles
列出将要编译的文件。
tsc --listFiles--listFilesOnly
只列出将要编译的文件,不进行编译。
tsc --listFilesOnly--traceResolution
跟踪模块解析过程。
tsc --traceResolution--extendedDiagnostics
显示详细的诊断信息。
tsc --extendedDiagnostics--pretty
美化错误信息。
tsc --pretty4. 示例命令
# 编译单个文件
tsc index.ts
# 编译多个文件
tsc file1.ts file2.ts file3.ts
# 编译整个项目(使用 tsconfig.json)
tsc
# 监视模式编译
tsc --watch
# 只进行类型检查,不生成文件
tsc --noEmit
# 指定输出目录
tsc --outDir ./dist
# 生成源映射和声明文件
tsc --sourceMap --declaration
# 启用严格模式
tsc --strict14. TypeScript 中的类型推断是如何工作的?
Details
类型推断是 TypeScript 中一种强大的特性,它允许编译器根据上下文自动推断变量的类型,而不需要显式指定类型。
类型推断的基本原理
TypeScript 编译器会根据变量的初始值、函数的返回值、表达式的类型等信息,自动推断变量的类型。
类型推断的场景
变量初始化:
typescriptlet x = 10; // 推断为 number 类型 let y = "hello"; // 推断为 string 类型 let z = true; // 推断为 boolean 类型函数返回值:
typescriptfunction add(a: number, b: number) { return a + b; // 推断返回值为 number 类型 } function greet(name: string) { return `Hello, ${name}!`; // 推断返回值为 string 类型 }函数参数:
typescript// 推断参数 x 为 number 类型,参数 y 为 number 类型 const add = (x, y) => x + y;数组和对象:
typescript// 推断为 number[] 类型 let numbers = [1, 2, 3, 4, 5]; // 推断为 { name: string; age: number } 类型 let person = { name: "Alice", age: 30 };类型守卫:
typescriptfunction processValue(value: string | number) { if (typeof value === "string") { // 推断为 string 类型 return value.toUpperCase(); } else { // 推断为 number 类型 return value.toFixed(2); } }泛型推断:
typescriptfunction identity<T>(arg: T): T { return arg; } // 推断 T 为 string 类型 let result = identity("hello"); // 推断 T 为 number 类型 let result2 = identity(10);
类型推断的规则
最佳通用类型:当推断数组或联合类型时,TypeScript 会寻找最适合的通用类型。
typescript// 推断为 (string | number)[] 类型 let values = [1, "hello", 2, "world"];上下文类型:当变量在特定上下文中使用时,TypeScript 会根据上下文推断类型。
typescriptwindow.onmousedown = function(event) { // 推断 event 为 MouseEvent 类型 console.log(event.button); };类型兼容性:TypeScript 会根据类型兼容性规则推断类型。
typescriptinterface 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;
类型推断的局限性
复杂表达式:对于复杂的表达式,TypeScript 可能无法准确推断类型。
typescript// 可能推断为 any 类型 let complex = someFunction() && anotherFunction();函数重载:对于函数重载,TypeScript 可能无法推断正确的类型。
typescriptfunction foo(x: string): string; function foo(x: number): number; function foo(x: any): any { return x; } // 推断为 any 类型 let result = foo("hello");动态类型:对于动态生成的类型,TypeScript 无法推断类型。
typescript// 推断为 any 类型 let dynamic = eval("({ name: 'Alice' })");
类型推断的最佳实践
显式类型标注:对于重要的变量和函数参数,建议显式标注类型,提高代码可读性和可维护性。
利用类型推断:对于简单的变量和函数,可以利用类型推断,减少冗余的类型标注。
类型断言:当 TypeScript 无法正确推断类型时,可以使用类型断言。
类型守卫:对于联合类型,使用类型守卫可以帮助 TypeScript 推断更准确的类型。
15. TypeScript 中的接口与类有什么区别?
Details
接口(Interface)和类(Class)是 TypeScript 中两个重要的概念,它们有不同的用途和特点。
接口(Interface)
接口是 TypeScript 中用于定义对象结构和类型的一种方式,它只定义结构,不包含实现。
接口的特点
- 只定义结构:接口只定义对象的结构和类型,不包含具体的实现。
- 不能实例化:接口不能被实例化,只能被类实现或被其他接口扩展。
- 支持可选属性:接口可以定义可选属性,使用
?符号。 - 支持只读属性:接口可以定义只读属性,使用
readonly关键字。 - 支持方法签名:接口可以定义方法签名,但不包含方法体。
- 支持索引签名:接口可以定义索引签名,用于访问对象的属性。
- 支持合并:同名接口会自动合并。
接口的示例
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 中用于创建对象的蓝图,它包含属性和方法的实现。
类的特点
- 包含实现:类包含属性和方法的具体实现。
- 可以实例化:类可以使用
new关键字实例化。 - 支持继承:类可以继承其他类,使用
extends关键字。 - 支持访问修饰符:类的成员可以使用
public、private、protected等访问修饰符。 - 支持构造函数:类可以定义构造函数,用于初始化对象。
- 支持静态成员:类可以定义静态属性和方法,使用
static关键字。 - 支持抽象类:类可以是抽象的,使用
abstract关键字,不能直接实例化。
类的示例
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) |
| 构造函数 | 不支持构造函数 | 支持构造函数 |
| 静态成员 | 不支持静态成员 | 支持静态成员 |
| 抽象类 | 不支持 | 支持抽象类 |
| 合并 | 支持同名接口合并 | 不支持同名类合并 |
接口与类的使用场景
接口:
- 定义对象的结构和类型
- 作为类的实现契约
- 定义函数的参数和返回值类型
- 定义模块的导出类型
类:
- 创建对象的蓝图
- 实现具体的业务逻辑
- 封装数据和行为
- 实现继承和多态
最佳实践
接口用于定义契约:使用接口定义对象的结构和类型,作为类的实现契约。
类用于实现逻辑:使用类实现具体的业务逻辑,封装数据和行为。
接口与类结合使用:使用接口定义类的结构,使用类实现具体的逻辑。
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 项目:
# 使用 Vite
npm create vite@latest my-react-app -- --template react-ts
# 使用 Create React App
npx create-react-app my-react-app --template typescript2. 组件类型定义
函数组件
使用 FC(Function Component)类型:
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;类组件
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. 类型定义文件
对于第三方库,可能需要安装对应的类型定义文件:
# 安装 React 类型定义
npm install --save-dev @types/react @types/react-dom
# 安装其他库的类型定义
npm install --save-dev @types/lodash4. 自定义钩子类型
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. 事件处理类型
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)类型
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. 最佳实践
使用泛型组件:
typescriptinterface 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> ); }使用类型断言谨慎:
typescript// 避免 const element = document.getElementById('root') as HTMLDivElement; // 更好的方式 const element = document.getElementById('root'); if (element instanceof HTMLDivElement) { // 这里 element 被推断为 HTMLDivElement }使用
as const断言:typescriptconst colors = ['red', 'green', 'blue'] as const; type Color = typeof colors[number]; // 'red' | 'green' | 'blue'使用
React.ReactNode作为子元素类型:typescriptinterface Props { children: React.ReactNode; }
17. TypeScript 与 Node.js 如何集成?
Details
TypeScript 与 Node.js 可以很好地集成,提供类型安全和更好的开发体验。以下是 TypeScript 与 Node.js 集成的主要方式:
1. 项目初始化
创建 TypeScript Node.js 项目:
# 创建目录
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 --init2. 配置 tsconfig.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
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. 安装依赖
# 安装 Express
npm install express
npm install --save-dev @types/express5. 运行脚本
在 package.json 中添加脚本:
{
"scripts": {
"start": "node dist/index.js",
"build": "tsc",
"dev": "ts-node src/index.ts"
}
}运行开发服务器:
npm run dev构建并运行:
npm run build
npm start6. 类型定义
自定义类型
// 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;
}服务层类型
// 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. 中间件类型
// 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. 路由类型
// 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. 最佳实践
使用环境变量类型:
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 || '' };使用类型保护:
typescriptfunction isUser(obj: any): obj is User { return obj && typeof obj === 'object' && 'id' in obj && 'name' in obj && 'email' in obj; }使用泛型工具类型:
typescript// 从 User 类型中排除某些属性 type UserWithoutId = Omit<User, 'id'>; // 从 User 类型中选择某些属性 type UserBasicInfo = Pick<User, 'name' | 'email'>; // 使 User 类型的所有属性变为可选 type UserPartial = Partial<User>; // 使 User 类型的所有属性变为必需 type UserRequired = Required<User>;使用
ts-node-dev进行开发:bashnpm install --save-dev ts-node-dev在 package.json 中添加脚本:
json{ "scripts": { "dev": "ts-node-dev src/index.ts" } }
18. TypeScript 的最佳实践有哪些?
Details
TypeScript 的最佳实践可以帮助你编写更清晰、更可维护、更类型安全的代码。以下是一些 TypeScript 的最佳实践:
1. 类型定义
使用接口和类型别名
- 接口:用于定义对象的结构和类型,适合用于类的实现和接口的扩展。
- 类型别名:用于定义复杂类型,如联合类型、交叉类型、元组类型等。
// 接口
interface User {
id: string;
name: string;
email: string;
}
// 类型别名
type UserId = string | number;
type UserWithOptionalEmail = Partial<User>;
type UserRequired = Required<User>;避免使用 any 类型
any 类型会禁用 TypeScript 的类型检查,应尽量避免使用。如果确实需要,可以考虑使用 unknown 类型。
// 避免
let data: any = fetchData();
// 更好的方式
let data: unknown = fetchData();
if (typeof data === 'string') {
// 这里 data 被推断为 string 类型
}使用字面量类型
字面量类型可以使代码更精确,减少错误。
// 字符串字面量类型
type Direction = 'up' | 'down' | 'left' | 'right';
// 数字字面量类型
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
// 布尔字面量类型
type BooleanLiteral = true | false;2. 代码组织
使用模块
使用 ES6 模块语法(import 和 export)组织代码,避免使用命名空间。
// 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分离类型定义
将类型定义分离到单独的文件中,提高代码的可维护性。
// 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 推断更准确的类型,提高代码的类型安全。
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();
}
}使用泛型
泛型可以使代码更加灵活和可复用,同时保持类型安全。
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 中启用严格模式,提高类型安全。
{
"compilerOptions": {
"strict": true
}
}4. 性能优化
使用 const enum
const enum 会在编译时内联枚举值,提高性能。
const enum Direction {
Up,
Down,
Left,
Right
}
let direction = Direction.Up; // 编译为:let direction = 0;使用 as const 断言
as const 断言可以使类型更加精确,避免不必要的类型检查。
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' }避免过度使用类型断言
过度使用类型断言会降低类型安全,应尽量避免。
// 避免
const element = document.getElementById('root') as HTMLDivElement;
// 更好的方式
const element = document.getElementById('root');
if (element instanceof HTMLDivElement) {
// 这里 element 被推断为 HTMLDivElement
}5. 工具和配置
使用 ESLint
使用 ESLint 检查 TypeScript 代码,提高代码质量。
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin创建 .eslintrc.js 文件:
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
rules: {
// 自定义规则
}
};使用 Prettier
使用 Prettier 格式化 TypeScript 代码,保持代码风格一致。
npm install --save-dev prettier创建 .prettierrc 文件:
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}合理配置 tsconfig.json
根据项目需求合理配置 tsconfig.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. 代码可读性
使用有意义的类型名称
使用有意义的类型名称,提高代码的可读性。
// 好的命名
type UserId = string | number;
type EmailAddress = string;
type DateString = string;
// 不好的命名
type T = string | number;
type S = string;
type D = string;使用 JSDoc 注释
使用 JSDoc 注释为类型和函数添加文档,提高代码的可读性。
/**
* 用户接口
*/
interface User {
/**
* 用户 ID
*/
id: string;
/**
* 用户名称
*/
name: string;
/**
* 用户邮箱
*/
email: string;
}
/**
* 加法函数
* @param a 第一个加数
* @param b 第二个加数
* @returns 两个数的和
*/
function add(a: number, b: number): number {
return a + b;
}使用类型推断
对于简单的变量和函数,可以利用类型推断,减少冗余的类型标注。
// 利用类型推断
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 enum和as 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 } }限制编译范围:使用
include和exclude限制编译范围。json{ "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts"] }使用
composite项目:对于大型项目,使用composite项目提高编译速度。json{ "compilerOptions": { "composite": true } }
2. 使用构建工具
使用 esbuild:esbuild 是一个快速的 JavaScript/TypeScript 构建工具,可以显著提高编译速度。
bashnpm install esbuild使用 SWC:SWC 是一个快速的 TypeScript/JavaScript 编译器,可以替代 Babel。
bashnpm install @swc/core @swc/cli使用 Webpack 或 Vite:使用现代构建工具,它们通常有更好的缓存机制和并行编译能力。
3. 代码组织
- 拆分大型文件:将大型文件拆分为多个小文件,提高编译速度。
- 避免循环依赖:循环依赖会增加编译时间,应尽量避免。
- 使用模块联邦:对于大型项目,使用模块联邦可以减少编译时间。
2. 运行时性能优化
1. 类型相关优化
使用
const enum:const enum会在编译时内联枚举值,减少运行时开销。typescriptconst enum Direction { Up, Down, Left, Right }使用
as const断言:as const断言可以使类型更加精确,避免不必要的类型检查。typescriptconst colors = ['red', 'green', 'blue'] as const; type Color = typeof colors[number];避免过度使用泛型:过度使用泛型会增加运行时开销,应合理使用。
避免使用
any类型:any类型会禁用类型检查,可能导致运行时错误。
2. 代码优化
使用
Map和Set:对于频繁查找的场景,使用Map和Set可以提高性能。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可以提高性能。typescriptconst config = Object.freeze({ apiUrl: 'https://api.example.com', timeout: 10000 });使用
Array.from和Array.of:对于数组操作,使用Array.from和Array.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);使用
WeakMap和WeakSet:对于临时引用,使用WeakMap和WeakSet可以避免内存泄漏。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.time和console.timeEnd测量代码执行时间。typescriptconsole.time('processData'); // 处理数据 console.timeEnd('processData');使用
performance.now():使用performance.now()测量更精确的时间。typescriptconst start = performance.now(); // 处理数据 const end = performance.now(); console.log(`Processed in ${end - start}ms`);
2. 监控编译时间
使用
tsc --extendedDiagnostics:使用tsc --extendedDiagnostics查看编译时间和内存使用情况。bashnpx tsc --extendedDiagnostics使用
speed-measure-webpack-plugin:对于 Webpack 项目,使用speed-measure-webpack-plugin分析构建时间。bashnpm install --save-dev speed-measure-webpack-plugin
5. 最佳实践
- 合理使用类型:使用适当的类型,避免过度使用复杂类型。
- 优化导入:使用
import语句的具体导入,避免导入整个模块。 - 使用
readonly:对于不需要修改的属性,使用readonly关键字。 - 使用
as const:对于常量值,使用as const断言。 - 避免使用
eval:eval会降低性能,应尽量避免。 - 使用
Object.keys和Object.values:对于对象操作,使用Object.keys和Object.values可以提高性能。 - 使用
Promise.all:对于并行操作,使用Promise.all可以提高性能。 - 使用
async/await:对于异步操作,使用async/await可以提高代码可读性和性能。 - 避免使用
try/catch:try/catch会降低性能,应尽量避免在热路径中使用。 - 使用
Web Workers:对于密集计算,使用Web Workers可以提高性能。
6. 总结
TypeScript 的性能优化包括:
- 编译性能优化:合理配置 tsconfig.json、使用构建工具、优化代码组织
- 运行时性能优化:类型相关优化、代码优化、内存优化
- 开发工具优化:使用 TypeScript 语言服务插件、编辑器优化、构建流程优化
- 性能监控:使用性能分析工具、监控编译时间
- 最佳实践:合理使用类型、优化导入、使用
readonly和as const等
通过这些优化策略,可以提高 TypeScript 项目的编译速度和运行时性能,改善开发体验和用户体验。
20. TypeScript 的常见错误和解决方案
Details
TypeScript 中常见的错误类型和解决方案如下:
1. 类型错误
1.1 类型不匹配
错误示例:
let num: number = "123"; // 类型 'string' 不能赋值给类型 'number'解决方案:
- 确保类型匹配
- 使用类型转换typescript
let num: number = Number("123");
1.2 缺少属性
错误示例:
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 类型断言错误
错误示例:
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 模块解析错误
错误示例:
import { add } from './math'; // 找不到模块 './math'解决方案:
- 确保文件路径正确
- 确保文件存在
- 检查 tsconfig.json 中的模块解析配置json
{ "compilerOptions": { "moduleResolution": "node" } }
2.2 循环依赖
错误示例:
// a.ts
import { b } from './b';
export const a = b + 1;
// b.ts
import { a } from './a';
export const b = a + 1;解决方案:
- 重构代码,避免循环依赖
- 将共享代码提取到单独的文件
2.3 类型定义缺失
错误示例:
import * as _ from 'lodash'; // 找不到模块 'lodash' 的类型定义解决方案:
- 安装类型定义文件bash
npm install --save-dev @types/lodash - 创建自定义类型定义
3. 运行时错误
3.1 空值错误
错误示例:
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 类型断言错误
错误示例:
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 数组越界
错误示例:
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 配置错误
错误示例:
{
"compilerOptions": {
"target": "ES2018",
"module": "ES6",
"outDir": "./dist",
"rootDir": "./src"
}
}解决方案:
- 确保配置项正确
- 检查目标环境的兼容性
4.2 路径别名配置错误
错误示例:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}解决方案:
- 确保路径别名配置正确
- 确保构建工具支持路径别名
5. 最佳实践
使用严格模式:在 tsconfig.json 中启用严格模式,捕获更多类型错误。
json{ "compilerOptions": { "strict": true } }使用类型守卫:使用类型守卫处理联合类型和可能的空值。
使用可选链和空值合并:使用可选链 (
?.) 和空值合并 (??) 操作符处理可能的空值。typescriptconst name = user?.name ?? "Unknown";使用
unknown替代any:使用unknown类型代替any,提高类型安全。使用泛型:使用泛型提高代码复用性和类型安全。
使用
as const:使用as const断言提高类型精确性。使用 JSDoc 注释:使用 JSDoc 注释为类型和函数添加文档。
使用 ESLint:使用 ESLint 检查 TypeScript 代码,捕获更多错误。
使用 Prettier:使用 Prettier 格式化 TypeScript 代码,保持代码风格一致。
定期更新依赖:定期更新 TypeScript 和相关依赖,获取最新的类型定义和性能改进。
6. 总结
TypeScript 的常见错误包括:
- 类型错误:类型不匹配、缺少属性、类型断言错误
- 编译错误:模块解析错误、循环依赖、类型定义缺失
- 运行时错误:空值错误、类型断言错误、数组越界
- 配置错误:tsconfig.json 配置错误、路径别名配置错误
通过遵循最佳实践,可以减少这些错误的发生:
- 使用严格模式
- 使用类型守卫
- 使用可选链和空值合并
- 使用
unknown替代any - 使用泛型
- 使用
as const - 使用 JSDoc 注释
- 使用 ESLint
- 使用 Prettier
- 定期更新依赖
这些策略可以帮助你编写更类型安全、更可维护的 TypeScript 代码。
21. 请简述 TypeScript 中 interface 和 type 的差别
Details
在 TypeScript 中,interface 和 type 都用于定义类型,但它们有一些重要的区别和使用场景。以下是它们之间的主要差异:
1. 定义方式
interface:- 用于定义对象的结构,可以声明方法、属性等。
- 支持扩展(通过继承)和合并(同名的接口会自动合并)。
typescriptinterface Person { name: string; age: number; greet(): void; }type:- 用于定义任意类型,可以是基本类型、联合类型、元组等。
- 不能像接口那样自动合并。
typescripttype Person = { name: string; age: number; greet: () => void; };
2. 扩展能力
interface:- 可以通过继承(
extends)来扩展其他接口。 - 允许多个接口合并。
typescriptinterface Employee extends Person { employeeId: number; } interface Person { name: string; age: number; }- 可以通过继承(
type:- 可以通过交叉类型(
&)来组合多个类型。
typescripttype Employee = Person & { employeeId: number; };- 可以通过交叉类型(
3. 支持的类型
interface:- 主要用于定义对象的结构,不能直接表示基本类型的联合。
typescriptinterface A {} interface B {} // 不能这样定义联合类型 // interface C = A | B; // 这是错误的type:- 可以定义基本类型的联合、交叉、元组等。
typescripttype StringOrNumber = string | number; // 联合类型
4. 语法和语义
interface:- 更适合描述对象的形状(结构),通常用于构建面向对象的编程风格。
type:- 更灵活,适用于任何类型的定义。
5. 性能与使用建议
在性能方面,interface 和 type 的差异通常不明显,但在大型项目中,使用 interface 可以更好地利用 TypeScript 的类型合并特性。同时,interface 也更适合用于库和框架的 API 定义,因为它的合并特性使得扩展和修改变得更简单。
总结
- 选择
interface:当你需要定义一个对象的结构,并且可能会有多个同名接口合并时,使用interface更为合适。 - 选择
type:当你需要定义复杂的类型(如联合、交叉类型)时,或者不需要合并时,使用type更加灵活。