跳到主要内容

类型声明和定义✅

本主题涵盖TypeScript中类型声明和定义的核心概念,包括interface与type的选择、枚举类型、声明文件等关键知识点。

interface和type有什么区别?何时使用哪个?

答案

核心概念:

interfacetype都能描述对象类型,但有不同的特性和使用场景:

  • interface: 专门用于对象类型定义,支持声明合并,更适合API设计
  • type: 功能更强大,支持联合类型、计算类型等,适合复杂类型操作

示例说明:

// 1. 基础对象类型定义
interface UserInterface {
id: number;
name: string;
email: string;
}

type UserType = {
id: number;
name: string;
email: string;
};

// 两者在基础对象类型定义上等效

// 2. interface特有功能:声明合并
interface User {
id: number;
name: string;
}

interface User {
email: string; // 自动合并到User接口中
}

// 最终User接口包含:id, name, email

// type不支持声明合并,会报错
// type UserType = { id: number; name: string; }
// type UserType = { email: string; } // ❌ 重复定义错误

// 3. type特有功能:联合类型
type Status = 'loading' | 'success' | 'error';
type ID = string | number;

// interface无法直接定义联合类型
// interface Status = 'loading' | 'success' | 'error'; // ❌ 语法错误

// 4. type特有功能:计算类型
type KeysOfUser = keyof UserInterface; // "id" | "name" | "email"
type UserValues = UserInterface[keyof UserInterface]; // string | number

// 条件类型
type NonNullable<T> = T extends null | undefined ? never : T;
type SafeString = NonNullable<string | null>; // string

// 5. 函数类型定义对比
interface GreetFunction {
(name: string): string;
}

type GreetType = (name: string) => string;

// 两种方式都可以,但type更简洁

// 6. 继承/扩展对比
interface Animal {
name: string;
}

// interface继承
interface Dog extends Animal {
breed: string;
}

// type扩展(交叉类型)
type Cat = Animal & {
meow(): void;
};

// interface也可以扩展type
interface Fish extends UserType {
swim(): void;
}

// type也可以扩展interface
type Bird = Animal & {
fly(): void;
};

// 7. 索引签名
interface StringDictionary {
[key: string]: string;
}

type NumberDictionary = {
[key: string]: number;
};

// 8. 泛型支持
interface GenericInterface<T> {
value: T;
getValue(): T;
}

type GenericType<T> = {
value: T;
getValue(): T;
};

// 9. 递归类型定义
interface TreeNode {
value: string;
children?: TreeNode[];
}

type LinkedList<T> = {
value: T;
next?: LinkedList<T>;
};

// 10. 复杂类型操作(type独有)
interface User {
id: number;
name: string;
email: string;
age: number;
}

// 工具类型操作
type UserKeys = keyof User; // "id" | "name" | "email" | "age"
type UserStringProperties = {
[K in keyof User]: User[K] extends string ? K : never;
}[keyof User]; // "name" | "email"

type PartialUser = Partial<User>;
type RequiredUser = Required<User>;

// 11. 模块声明中的使用
declare global {
interface Window {
myCustomProperty: string;
}
}

// type无法在模块声明中合并
// declare global {
// type Window = Window & { myCustomProperty: string }; // ❌ 复杂且不直观
// }

// 12. 实际应用场景对比

// 场景1:API接口定义(推荐interface)
interface APIResponse<T> {
data: T;
status: number;
message: string;
}

interface UserAPI extends APIResponse<User> {
meta: {
total: number;
page: number;
};
}

// 场景2:配置选项(推荐type)
type DatabaseConfig = {
host: string;
port: number;
} & (
| { type: 'mysql'; charset: string }
| { type: 'postgres'; ssl: boolean }
);

// 场景3:状态管理(推荐type)
type Action =
| { type: 'SET_USER'; payload: User }
| { type: 'CLEAR_USER' }
| { type: 'UPDATE_USER'; payload: Partial<User> };

// 场景4:组件Props(推荐interface,便于扩展)
interface ButtonProps {
text: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
}

interface PrimaryButtonProps extends ButtonProps {
variant: 'primary';
icon?: string;
}

// 13. 性能对比
// interface在某些情况下编译性能更好
interface LargeInterface {
prop1: string;
prop2: number;
// ... 100个属性
}

// type在复杂计算类型中可能较慢
type ComputedType<T> = {
[K in keyof T]: T[K] extends string ? `str_${K}` : `num_${K}`;
};

// 14. 选择建议总结
/*
使用interface当:
- 定义对象的形状
- 需要声明合并
- 创建公共API
- 面向对象设计
- 需要被继承/实现

使用type当:
- 联合类型
- 交叉类型
- 计算类型
- 函数类型
- 复杂类型操作
- 工具类型定义
*/

面试官视角:

这是TypeScript中最重要的概念区分之一:

  • 要点清单: 理解两者的基本区别;掌握声明合并特性;知道各自的适用场景;能举例说明选择依据
  • 加分项: 提到编译性能差异;理解模块声明中的使用;掌握复杂类型设计的最佳实践
  • 常见失误: 混用场景不当;不理解声明合并机制;过度复杂化类型定义

延伸阅读:

枚举和常量枚举有什么区别?

答案

核心概念:

枚举用于定义命名常量集合,提高代码可读性。常量枚举在编译时被内联,性能更好但功能受限:

  • enum: 运行时存在,可以反向映射,更灵活
  • const enum: 编译时内联,性能更好,体积更小

示例说明:

// 1. 普通枚举
enum Color {
Red, // 0
Green, // 1
Blue // 2
}

enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}

// 编译后的JavaScript(简化版)
/*
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Green"] = 1] = "Green";
Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
*/

// 使用普通枚举
const selectedColor: Color = Color.Red;
const colorName: string = Color[0]; // "Red" - 反向映射

console.log(Color.Red); // 0
console.log(Color[0]); // "Red"
console.log(typeof Color); // "object"

// 2. 常量枚举
const enum Theme {
Light = "light",
Dark = "dark",
Auto = "auto"
}

const enum Status {
Loading, // 0
Success, // 1
Error // 2
}

// 使用常量枚举
const currentTheme: Theme = Theme.Dark;
const loadingStatus: Status = Status.Loading;

// 编译后的JavaScript
/*
const currentTheme = "dark"; // 直接内联值
const loadingStatus = 0; // 直接内联值
*/

// 3. 混合类型枚举(不推荐)
enum Mixed {
No = 0,
Yes = "YES"
}

// 4. 计算成员和常量成员
enum FileAccess {
// 常量成员
None,
Read = 1 << 1, // 2
Write = 1 << 2, // 4
ReadWrite = Read | Write, // 6

// 计算成员
G = "123".length // 3
}

// 5. 常量枚举的限制
const enum LimitedEnum {
A = 1,
B = 2
}

// ❌ 常量枚举不支持以下操作:
// console.log(LimitedEnum); // 错误:不存在运行时对象
// const keys = Object.keys(LimitedEnum); // 错误:无法获取keys
// const value = LimitedEnum[1]; // 错误:无法反向映射

// 6. 字符串枚举 vs 数字枚举
enum NumericEnum {
A, // 0
B, // 1
C // 2
}

enum StringEnum {
A = "a",
B = "b",
C = "c"
}

// 数字枚举支持反向映射
console.log(NumericEnum[0]); // "A"
console.log(NumericEnum.A); // 0

// 字符串枚举不支持反向映射
// console.log(StringEnum["a"]); // ❌ undefined
console.log(StringEnum.A); // "a" ✅

// 7. 枚举作为类型
function handleColor(color: Color): string {
switch (color) {
case Color.Red:
return "红色";
case Color.Green:
return "绿色";
case Color.Blue:
return "蓝色";
default:
const exhaustiveCheck: never = color; // 穷尽检查
return exhaustiveCheck;
}
}

// 8. 枚举的高级用法
enum EventType {
Click = "click",
KeyDown = "keydown",
Focus = "focus"
}

// 枚举值的联合类型
type EventHandler<T extends EventType> = (event: Event) => void;

const clickHandler: EventHandler<EventType.Click> = (e) => {
console.log("Clicked");
};

// 9. 使用keyof获取枚举键
enum APIStatus {
Pending = "pending",
Success = "success",
Error = "error"
}

type StatusKey = keyof typeof APIStatus; // "Pending" | "Success" | "Error"
type StatusValue = APIStatus; // "pending" | "success" | "error"

// 10. 枚举的实用工具类型
function getEnumValues<T extends Record<string | number, string | number>>(
enumObject: T
): T[keyof T][] {
return Object.values(enumObject) as T[keyof T][];
}

const colorValues = getEnumValues(Color); // (string | number)[]
const directionValues = getEnumValues(Direction); // string[]

// 11. 性能对比
// 普通枚举 - 运行时开销
function useNormalEnum() {
return Direction.Up; // 需要对象属性访问
}

// 常量枚举 - 零运行时开销
function useConstEnum() {
return Theme.Light; // 编译时替换为 "light"
}

// 12. 编译选项的影响
// tsconfig.json: "preserveConstEnums": true
// 保留常量枚举的运行时代码,兼得两者优势

// 13. 最佳实践对比
// 推荐:使用字符串枚举提高调试体验
enum LogLevel {
DEBUG = "debug",
INFO = "info",
WARN = "warn",
ERROR = "error"
}

// 而非数字枚举
enum BadLogLevel {
DEBUG, // 0 - 调试时不直观
INFO, // 1
WARN, // 2
ERROR // 3
}

// 14. 枚举 vs 联合类型
// 枚举方式
enum Theme1 {
Light = "light",
Dark = "dark"
}

// 联合类型方式
type Theme2 = "light" | "dark";

// 枚举的优势:
// - 可以添加方法
// - 命名空间
// - 更好的IDE支持

// 联合类型的优势:
// - 更简洁
// - 类型更精确
// - 无运行时开销

// 15. 选择建议
/*
使用普通枚举当:
- 需要反向映射
- 需要运行时枚举遍历
- API设计中需要稳定的常量
- 需要为枚举添加方法

使用常量枚举当:
- 性能敏感场景
- 打包体积要求严格
- 简单的常量集合
- 编译时确定的值

使用联合类型当:
- 简单的字符串/数字常量
- 类型更重要than值
- 函数式编程风格
- 需要类型推断
*/

// 16. 现代替代方案
// 使用 as const 断言
const THEME = {
LIGHT: 'light',
DARK: 'dark'
} as const;

type ThemeKeys = keyof typeof THEME; // "LIGHT" | "DARK"
type ThemeValues = typeof THEME[ThemeKeys]; // "light" | "dark"

面试官视角:

枚举是TypeScript独有的特性,体现了其对JavaScript的增强:

  • 要点清单: 理解枚举的编译机制;掌握常量枚举的性能优势;了解字符串枚举vs数字枚举;知道反向映射机制
  • 加分项: 提到现代替代方案(as const);理解编译选项的影响;掌握枚举的高级用法
  • 常见失误: 混合类型枚举的使用;常量枚举的限制不清楚;不理解反向映射的适用场景

延伸阅读:

const和readonly有什么区别?

答案

核心概念:

constreadonly都用于创建不可变数据,但作用层面不同:

  • const: 变量级别的不可变,运行时检查,不能重新赋值
  • readonly: 属性级别的不可变,编译时检查,属性不可修改

示例说明:

// 1. const - 变量不可重新赋值
const name = "Alice";
const age = 25;
const config = { apiUrl: "https://api.com", timeout: 5000 };

// name = "Bob"; // ❌ 错误:不能重新赋值const变量
// age = 30; // ❌ 错误:不能重新赋值const变量
config.apiUrl = "https://new-api.com"; // ✅ 可以修改对象属性
config.timeout = 3000; // ✅ 可以修改对象属性

// 2. readonly - 属性不可修改
interface User {
readonly id: number;
name: string;
readonly email: string;
}

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

user.name = "Bob"; // ✅ 可以修改非readonly属性
// user.id = 2; // ❌ 错误:不能修改readonly属性
// user.email = "bob@example.com"; // ❌ 错误:不能修改readonly属性

// 3. readonly数组和元组
const numbers: readonly number[] = [1, 2, 3, 4, 5];
const tuple: readonly [string, number] = ["hello", 42];

// numbers.push(6); // ❌ 错误:readonly数组不能修改
// numbers[0] = 10; // ❌ 错误:不能修改数组元素
console.log(numbers[0]); // ✅ 可以读取元素

// 4. ReadonlyArray vs Array
const mutableArray: number[] = [1, 2, 3];
const readonlyArray: ReadonlyArray<number> = [1, 2, 3];
// 等价写法:const readonlyArray: readonly number[] = [1, 2, 3];

mutableArray.push(4); // ✅ 可以修改
// readonlyArray.push(4); // ❌ 错误:ReadonlyArray没有push方法

// 5. 深度readonly vs 浅层readonly
interface Config {
readonly name: string;
readonly database: {
host: string;
port: number;
};
}

const config1: Config = {
name: "MyApp",
database: {
host: "localhost",
port: 3306
}
};

// config1.name = "NewApp"; // ❌ 错误:readonly属性
// config1.database = { ... }; // ❌ 错误:readonly属性
config1.database.host = "127.0.0.1"; // ✅ 可以修改嵌套对象属性
config1.database.port = 5432; // ✅ 可以修改嵌套对象属性

// 6. 深度readonly工具类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepReadonly<T[P]>
: T[P];
};

type DeepReadonlyConfig = DeepReadonly<Config>;

const config2: DeepReadonlyConfig = {
name: "MyApp",
database: {
host: "localhost",
port: 3306
}
};

// config2.database.host = "127.0.0.1"; // ❌ 错误:深度readonly

// 7. 函数参数中的readonly
function processUsers(users: readonly User[]): number {
// users.push(newUser); // ❌ 错误:不能修改readonly数组
return users.length; // ✅ 可以读取
}

function updateUserName(user: User, name: string): User {
// 这里可以修改传入的user对象
user.name = name;
return user;
}

function updateUserNameReadonly(
user: Readonly<User>,
name: string
): User {
// user.name = name; // ❌ 错误:不能修改readonly对象

// 需要创建新对象
return {
...user,
name: name
};
}

// 8. 类中的readonly
class Person {
readonly id: number;
public name: string;
private readonly secret: string;

constructor(id: number, name: string, secret: string) {
this.id = id; // ✅ 构造函数中可以赋值
this.name = name;
this.secret = secret; // ✅ 构造函数中可以赋值
}

updateName(newName: string) {
this.name = newName; // ✅ 可以修改非readonly属性
// this.id = 999; // ❌ 错误:不能修改readonly属性
// this.secret = "new"; // ❌ 错误:不能修改readonly属性
}
}

// 9. readonly vs const在对象中的表现
const obj1 = {
readonly: "value" // 注意:这里readonly只是属性名,不是修饰符
};

obj1.readonly = "new value"; // ✅ 可以修改,因为没有readonly修饰符

const obj2: { readonly value: string } = {
value: "readonly value"
};

// obj2.value = "new value"; // ❌ 错误:readonly属性不可修改

// 10. 类型兼容性
interface MutableUser {
name: string;
age: number;
}

interface ReadonlyUser {
readonly name: string;
readonly age: number;
}

const mutableUser: MutableUser = { name: "Alice", age: 25 };
const readonlyUser: ReadonlyUser = mutableUser; // ✅ 可变类型可以赋值给readonly类型

const anotherMutableUser: MutableUser = readonlyUser; // ❌ readonly类型不能赋值给可变类型

// 11. 实际应用场景对比

// 使用const:配置常量
const API_ENDPOINTS = {
USERS: '/api/users',
POSTS: '/api/posts'
} as const; // as const确保字面量类型

// 使用readonly:API接口设计
interface APIResponse<T> {
readonly data: T;
readonly status: number;
readonly timestamp: number;
}

function handleResponse<T>(response: APIResponse<T>): T {
// response.status = 500; // ❌ 不能修改响应状态
return response.data;
}

// 12. 函数签名中的readonly参数
function calculateSum(numbers: readonly number[]): number {
// 函数承诺不会修改传入的数组
return numbers.reduce((sum, num) => sum + num, 0);
}

const scores = [85, 92, 78, 96];
const total = calculateSum(scores); // 调用者确信数组不会被修改

// 13. readonly与不可变数据结构
class ImmutableList<T> {
private readonly items: readonly T[];

constructor(items: T[]) {
this.items = [...items]; // 创建副本
}

get(index: number): T | undefined {
return this.items[index];
}

add(item: T): ImmutableList<T> {
return new ImmutableList([...this.items, item]);
}

remove(index: number): ImmutableList<T> {
const newItems = [...this.items];
newItems.splice(index, 1);
return new ImmutableList(newItems);
}

get length(): number {
return this.items.length;
}
}

// 14. 编译时 vs 运行时
// readonly是编译时检查
const readonlyObj: { readonly prop: string } = { prop: "value" };
// 在JavaScript运行时,实际上可以被修改(不推荐这样做)
(readonlyObj as any).prop = "modified"; // 类型断言绕过编译检查

// const是运行时检查
const constVar = "value";
// constVar = "modified"; // 运行时会抛出错误

// 15. 最佳实践总结
/*
使用const当:
- 声明不会重新赋值的变量
- 定义配置常量
- 函数式编程中的不变量
- 防止意外的重新赋值

使用readonly当:
- 接口/类型中的不可变属性
- API响应类型设计
- 函数参数承诺不修改
- 创建不可变的数据结构
- 防止属性被意外修改
*/

// 16. 组合使用示例
const config: readonly {
readonly apiUrl: string;
readonly features: readonly string[];
} = {
apiUrl: "https://api.example.com",
features: ["auth", "payments", "analytics"]
};

// config = newConfig; // ❌ const:不能重新赋值
// config.apiUrl = "new-url"; // ❌ readonly:不能修改属性
// config.features.push("newFeature"); // ❌ readonly array:不能修改数组

面试官视角:

const和readonly的区别体现了TypeScript类型安全的不同层面:

  • 要点清单: 理解变量级vs属性级的区别;掌握编译时vs运行时检查;了解深度readonly的概念;知道类型兼容性规则
  • 加分项: 提到不可变数据结构设计;理解JavaScript运行时的实际行为;掌握实际应用场景的选择
  • 常见失误: 混淆作用域和检查时机;对深度readonly的理解不准确;类型兼容性判断错误

延伸阅读:

in操作符在TypeScript中的作用是什么?

答案

核心概念:

in操作符用于检查对象是否包含指定属性,在TypeScript中主要用于类型守卫、联合类型缩减和映射类型操作。它提供了运行时的属性检查和编译时的类型推断。

示例说明:

// 1. 基础属性检查
interface User {
name: string;
age: number;
email?: string;
}

const user: User = {
name: "Alice",
age: 25,
email: "alice@example.com"
};

// 检查必需属性
if ('name' in user) {
console.log(user.name); // ✅ TypeScript知道name属性存在
}

// 检查可选属性
if ('email' in user) {
console.log(user.email.length); // ✅ TypeScript知道email不是undefined
}

// 2. 类型守卫中的应用
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}

interface Circle {
kind: 'circle';
radius: number;
}

type Shape = Rectangle | Circle;

function getArea(shape: Shape): number {
if ('width' in shape) {
// TypeScript推断shape为Rectangle类型
return shape.width * shape.height;
} else {
// TypeScript推断shape为Circle类型
return Math.PI * shape.radius ** 2;
}
}

// 3. 复杂对象的类型缩减
interface Bird {
type: 'bird';
fly(): void;
layEggs(): void;
}

interface Fish {
type: 'fish';
swim(): void;
layEggs(): void;
}

interface Dog {
type: 'dog';
bark(): void;
run(): void;
}

type Animal = Bird | Fish | Dog;

function handleAnimal(animal: Animal) {
if ('fly' in animal) {
animal.fly(); // animal被缩减为Bird类型
animal.layEggs();
} else if ('swim' in animal) {
animal.swim(); // animal被缩减为Fish类型
animal.layEggs();
} else {
animal.bark(); // animal被缩减为Dog类型
animal.run();
}
}

// 4. API响应处理
interface SuccessResponse {
success: true;
data: any;
}

interface ErrorResponse {
success: false;
error: string;
code: number;
}

type APIResponse = SuccessResponse | ErrorResponse;

function handleResponse(response: APIResponse) {
if ('error' in response) {
// response被缩减为ErrorResponse
console.error(`Error ${response.code}: ${response.error}`);
} else {
// response被缩减为SuccessResponse
console.log('Success:', response.data);
}
}

// 5. 映射类型中的in操作符
type Optional<T> = {
[K in keyof T]?: T[K];
};

type Required<T> = {
[K in keyof T]-?: T[K];
};

interface Config {
apiUrl: string;
timeout?: number;
retries?: number;
}

type OptionalConfig = Optional<Config>;
// { apiUrl?: string; timeout?: number; retries?: number; }

type RequiredConfig = Required<Config>;
// { apiUrl: string; timeout: number; retries: number; }

// 6. 条件类型中的属性检查
type HasProperty<T, K extends string | number | symbol> =
K extends keyof T ? true : false;

type UserHasName = HasProperty<User, 'name'>; // true
type UserHasId = HasProperty<User, 'id'>; // false

// 7. 运行时动态属性检查
function hasProperty<T extends object, K extends string>(
obj: T,
key: K
): obj is T & Record<K, unknown> {
return key in obj;
}

const someObject: unknown = {
name: "test",
value: 42
};

if (hasProperty(someObject, 'name')) {
// someObject被缩减为包含name属性的类型
console.log(someObject.name); // ✅ 类型安全
}

// 8. 嵌套属性检查
interface NestedConfig {
database?: {
host: string;
port: number;
};
cache?: {
ttl: number;
};
}

function setupConfig(config: NestedConfig) {
if ('database' in config && config.database) {
if ('host' in config.database) {
console.log(`Connecting to ${config.database.host}`);
}
}
}

// 9. 数组和对象的区分
function processData(data: string[] | { [key: string]: string }) {
if ('length' in data) {
// data被推断为string[]
data.forEach(item => console.log(item));
} else {
// data被推断为{ [key: string]: string }
for (const key in data) {
console.log(`${key}: ${data[key]}`);
}
}
}

// 10. 可选链与in操作符的组合
interface DeepConfig {
features?: {
auth?: {
providers?: string[];
};
};
}

function checkAuthProviders(config: DeepConfig): boolean {
return 'features' in config &&
config.features !== undefined &&
'auth' in config.features &&
config.features.auth !== undefined &&
'providers' in config.features.auth &&
Array.isArray(config.features.auth.providers);
}

// 更简洁的写法(使用可选链)
function checkAuthProvidersSimple(config: DeepConfig): boolean {
return Array.isArray(config.features?.auth?.providers);
}

// 11. in操作符与typeof的组合
function processValue(value: string | number | object) {
if (typeof value === 'object' && value !== null) {
if ('length' in value && typeof (value as any).length === 'number') {
// 可能是类数组对象
console.log('Array-like object with length:', (value as any).length);
} else if ('toString' in value) {
// 普通对象
console.log('Object:', value.toString());
}
}
}

// 12. 高级类型守卫
interface Container<T> {
value: T;
type: string;
}

interface StringContainer extends Container<string> {
type: 'string';
toUpperCase(): string;
}

interface NumberContainer extends Container<number> {
type: 'number';
toFixed(digits: number): string;
}

type AnyContainer = StringContainer | NumberContainer;

function processContainer(container: AnyContainer) {
// 使用判别联合类型更准确
if (container.type === 'string') {
container.toUpperCase(); // ✅ 更准确的类型推断
}

// 使用in操作符的备选方案
if ('toUpperCase' in container) {
container.toUpperCase(); // ✅ 也能工作,但不如上面准确
}
}

// 13. 性能考虑
// in操作符相对高效,但在热代码路径中要注意
function fastTypeCheck(value: { a: number } | { b: string }): string {
// 高效的属性检查
return 'a' in value ? 'has a' : 'has b';
}

// 14. 边界情况处理
function safePropertyCheck(obj: unknown, prop: string): boolean {
// 安全的属性检查
return typeof obj === 'object' &&
obj !== null &&
prop in obj;
}

// 15. 实际应用:表单验证
interface FormField {
value: string;
required?: boolean;
pattern?: RegExp;
minLength?: number;
maxLength?: number;
}

function validateField(field: FormField): string[] {
const errors: string[] = [];

if ('required' in field && field.required && !field.value) {
errors.push('Field is required');
}

if ('minLength' in field && field.value.length < field.minLength!) {
errors.push(`Minimum length is ${field.minLength}`);
}

if ('maxLength' in field && field.value.length > field.maxLength!) {
errors.push(`Maximum length is ${field.maxLength}`);
}

if ('pattern' in field && !field.pattern!.test(field.value)) {
errors.push('Field format is invalid');
}

return errors;
}

// 16. 与其他类型守卫的比较
function compareTypeGuards(value: { type: 'A'; propA: string } | { type: 'B'; propB: number }) {
// 方法1:使用判别联合(推荐)
if (value.type === 'A') {
console.log(value.propA);
}

// 方法2:使用in操作符
if ('propA' in value) {
console.log(value.propA);
}

// 方法1更准确,因为它基于明确的类型标识
}

面试官视角:

in操作符是TypeScript类型安全的重要工具:

  • 要点清单: 理解in操作符的类型守卫作用;掌握与联合类型的配合;了解映射类型中的应用;能处理复杂对象的属性检查
  • 加分项: 提到性能考虑;掌握与其他类型守卫的对比;理解边界情况的处理
  • 常见失误: 忽略null检查导致运行时错误;与判别联合类型的使用场景混淆;复杂嵌套检查时逻辑错误

延伸阅读:

什么是声明文件?如何编写.d.ts文件?

答案

核心概念:

声明文件(.d.ts)提供TypeScript类型信息而不包含实现代码,用于为JavaScript库添加类型支持、扩展全局类型或声明模块。它是TypeScript生态系统的重要组成部分。

示例说明:

// 1. 基础声明文件结构
// math-utils.d.ts
declare module 'math-utils' {
export function add(a: number, b: number): number;
export function multiply(a: number, b: number): number;
export const PI: number;

export interface CalculatorOptions {
precision: number;
roundingMode: 'ceil' | 'floor' | 'round';
}

export class Calculator {
constructor(options?: CalculatorOptions);
calculate(expression: string): number;
reset(): void;
}
}

// 使用声明的模块
import { add, Calculator } from 'math-utils';
const result = add(1, 2);
const calc = new Calculator({ precision: 2 });

// 2. 全局类型声明
// globals.d.ts
declare global {
interface Window {
customAPI: {
version: string;
getUserData(): Promise<UserData>;
};
analytics?: {
track(event: string, data?: Record<string, any>): void;
};
}

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

var MY_GLOBAL_CONSTANT: string;

namespace MyLibrary {
interface Config {
apiKey: string;
endpoint: string;
}

function initialize(config: Config): void;
function destroy(): void;
}
}

// 使用全局声明
window.customAPI.getUserData().then(user => {
console.log(user.name);
});

window.analytics?.track('page_view');

// 3. 模块扩展
// express-extend.d.ts
declare module 'express' {
interface Request {
user?: {
id: string;
email: string;
roles: string[];
};
}
}

// 现在可以在Express应用中使用
// app.ts
import express from 'express';

const app = express();

app.use((req, res, next) => {
req.user = { id: '1', email: 'user@example.com', roles: ['user'] };
next();
});

app.get('/profile', (req, res) => {
console.log(req.user?.email); // TypeScript知道user属性存在
});

// 4. 第三方库的类型声明
// lodash-custom.d.ts
declare module 'lodash' {
interface LoDashStatic {
customMethod<T>(collection: T[], predicate: (item: T) => boolean): T[];
}
}

// 使用扩展的lodash
import _ from 'lodash';
const filtered = _.customMethod([1, 2, 3], x => x > 1);

// 5. 环境模块声明
// types/environment.d.ts
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
JWT_SECRET: string;
PORT?: string;
API_KEY?: string;
}
}

// 现在可以安全使用环境变量
const dbUrl: string = process.env.DATABASE_URL; // ✅ 类型安全
const port: string | undefined = process.env.PORT; // ✅ 正确的可选类型

// 6. 函数重载声明
// api-client.d.ts
declare module 'api-client' {
interface ApiClient {
// 函数重载
get(url: string): Promise<any>;
get<T>(url: string, options: { parse: true }): Promise<T>;
get(url: string, options: { parse: false }): Promise<string>;

post(url: string, data: any): Promise<any>;
post<T, R>(url: string, data: T): Promise<R>;
}

const client: ApiClient;
export = client;
}

// 7. 条件类型声明
// utility-types.d.ts
declare module 'utility-types' {
type NonNullable<T> = T extends null | undefined ? never : T;

type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};

export { NonNullable, DeepPartial, PickByType };
}

// 8. React组件库声明
// ui-components.d.ts
declare module 'ui-components' {
import { ComponentType, ReactNode } from 'react';

interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
onClick?: () => void;
children: ReactNode;
}

interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
children: ReactNode;
}

export const Button: ComponentType<ButtonProps>;
export const Modal: ComponentType<ModalProps>;
}

// 9. CSS模块声明
// css-modules.d.ts
declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}

declare module '*.module.scss' {
const classes: { [key: string]: string };
export default classes;
}

// 现在可以导入CSS模块
import styles from './component.module.css';

// 10. 文件类型声明
// file-types.d.ts
declare module '*.svg' {
const content: string;
export default content;
}

declare module '*.png' {
const content: string;
export default content;
}

declare module '*.json' {
const value: any;
export default value;
}

// 11. Worker声明
// worker.d.ts
declare module '*.worker.ts' {
class WebpackWorker extends Worker {
constructor();
}

export default WebpackWorker;
}

// 使用Worker
import MyWorker from './calculations.worker.ts';
const worker = new MyWorker();

// 12. 插件系统声明
// plugin-system.d.ts
declare module 'plugin-system' {
interface Plugin {
name: string;
version: string;
init(): void;
destroy(): void;
}

interface PluginContext {
registerHook(name: string, callback: Function): void;
executeHook(name: string, ...args: any[]): any[];
}

export class PluginManager {
constructor();
register(plugin: Plugin): void;
unregister(pluginName: string): void;
getContext(): PluginContext;
}
}

// 13. 数据库ORM声明
// orm.d.ts
declare module 'custom-orm' {
interface ModelDefinition {
[key: string]: {
type: 'string' | 'number' | 'boolean' | 'date';
required?: boolean;
unique?: boolean;
default?: any;
};
}

interface QueryBuilder<T> {
where(field: keyof T, value: any): QueryBuilder<T>;
orderBy(field: keyof T, direction: 'asc' | 'desc'): QueryBuilder<T>;
limit(count: number): QueryBuilder<T>;
execute(): Promise<T[]>;
}

export class Model<T> {
constructor(definition: ModelDefinition);
create(data: Partial<T>): Promise<T>;
findById(id: string): Promise<T | null>;
query(): QueryBuilder<T>;
}
}

// 14. 高级声明技巧
// advanced-declarations.d.ts

// 命名空间合并
declare namespace MyFramework {
interface Config {
theme: string;
}
}

declare namespace MyFramework {
interface Config {
language: string; // 自动合并到上面的Config中
}

function initialize(config: Config): void;
}

// 条件模块声明
declare module 'responsive-module' {
interface DesktopAPI {
openWindow(): void;
closeWindow(): void;
}

interface MobileAPI {
showModal(): void;
hideModal(): void;
}

const api: typeof window !== 'undefined'
? DesktopAPI
: MobileAPI;

export default api;
}

// 15. 项目配置文件
// tsconfig.json中的声明文件配置
/*
{
"compilerOptions": {
"declaration": true, // 生成.d.ts文件
"declarationMap": true, // 生成源映射
"typeRoots": [ // 类型根目录
"node_modules/@types",
"src/types"
],
"types": [ // 显式包含的类型
"node",
"jest",
"custom-types"
]
},
"include": [
"src/**/*",
"types/**/*" // 包含自定义类型目录
]
}
*/

// 16. 最佳实践示例
// types/index.d.ts - 项目主要类型声明
export interface User {
id: string;
email: string;
profile: UserProfile;
}

export interface UserProfile {
firstName: string;
lastName: string;
avatar?: string;
}

export type UserRole = 'admin' | 'user' | 'guest';

export interface ApiError {
code: string;
message: string;
details?: Record<string, any>;
}

// 声明文件的导入和导出
export * from './api-types';
export * from './ui-types';

// 17. 声明文件调试技巧
// 使用/// <reference>指令
/// <reference types="node" />
/// <reference path="./custom-types.d.ts" />

declare module 'debug-module' {
// 开发时的调试接口
interface DebugAPI {
log(message: string, data?: any): void;
warn(message: string): void;
error(error: Error): void;
}

const debug: DebugAPI;
export default debug;
}

面试官视角:

声明文件是TypeScript生态系统的关键组成部分:

  • 要点清单: 理解声明文件的作用和语法;掌握模块声明和全局声明;了解模块扩展技巧;知道常见的声明模式
  • 加分项: 能编写复杂的类型声明;理解声明合并机制;掌握项目配置和最佳实践
  • 常见失误: 声明语法错误;模块路径配置问题;声明文件的作用域理解错误;忽略类型安全

延伸阅读:

如何扩展已有的类型或模块?

答案

核心概念:

TypeScript支持通过模块扩展、接口合并、命名空间合并等方式扩展已有类型。这允许为第三方库添加自定义类型、扩展全局对象或增强现有API的类型定义。

示例说明:

// 1. 模块扩展 (Module Augmentation)
// 扩展Express的Request接口
declare module 'express-serve-static-core' {
interface Request {
user?: {
id: string;
email: string;
roles: string[];
};
session?: {
userId: string;
loginTime: Date;
};
}

interface Response {
apiSuccess<T>(data: T): void;
apiError(error: string, code?: number): void;
}
}

// 现在可以在Express应用中使用
import express from 'express';

const app = express();

app.use((req, res, next) => {
// 扩展Response方法
res.apiSuccess = function<T>(data: T) {
this.json({ success: true, data });
};

res.apiError = function(error: string, code = 400) {
this.status(code).json({ success: false, error });
};

next();
});

app.get('/profile', (req, res) => {
if (req.user) {
res.apiSuccess(req.user); // ✅ 类型安全
} else {
res.apiError('User not found', 404);
}
});

// 2. 全局接口扩展
declare global {
interface Window {
// 扩展Window对象
gtag?: (command: 'config' | 'event', target: string, params?: any) => void;
dataLayer?: any[];
customAPI?: {
version: string;
initialize(config: { apiKey: string }): Promise<void>;
track(event: string, properties?: Record<string, any>): void;
};
}

// 扩展Array原型
interface Array<T> {
shuffle(): Array<T>;
unique(): Array<T>;
groupBy<K extends string | number>(keyFn: (item: T) => K): Record<K, T[]>;
}

// 扩展String原型
interface String {
toCapitalCase(): string;
truncate(length: number, suffix?: string): string;
}

// 扩展Number原型
interface Number {
toFixedNumber(digits: number): number;
clamp(min: number, max: number): number;
}
}

// 实现扩展的方法
Array.prototype.shuffle = function<T>(this: T[]): T[] {
const array = [...this];
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};

Array.prototype.unique = function<T>(this: T[]): T[] {
return [...new Set(this)];
};

// 使用扩展的方法
const numbers = [1, 2, 3, 4, 5];
const shuffled = numbers.shuffle(); // ✅ 类型安全
const uniqueItems = [1, 2, 2, 3, 3, 4].unique(); // ✅ 类型安全

// 3. 第三方库扩展
// 扩展lodash
declare module 'lodash' {
interface LoDashStatic {
// 添加自定义方法
customChunk<T>(array: T[], size: number, fillValue?: T): T[][];
safeGet<T, K extends keyof T>(object: T, path: K): T[K] | undefined;
safeGet<T>(object: T, path: string): any | undefined;
}

interface LoDashImplicitWrapper<TValue> {
customChunk<T>(this: LoDashImplicitWrapper<T[]>, size: number, fillValue?: T): LoDashImplicitWrapper<T[][]>;
}
}

// 实现自定义lodash方法
import _ from 'lodash';

_.mixin({
customChunk: function<T>(array: T[], size: number, fillValue?: T): T[][] {
const chunks = _.chunk(array, size);
if (fillValue !== undefined && chunks.length > 0) {
const lastChunk = chunks[chunks.length - 1];
while (lastChunk.length < size) {
lastChunk.push(fillValue);
}
}
return chunks;
}
});

// 使用扩展的lodash方法
const chunked = _.customChunk([1, 2, 3, 4, 5], 2, 0); // [[1, 2], [3, 4], [5, 0]]

// 4. React组件扩展
declare module 'react' {
// 扩展HTML属性
interface HTMLAttributes<T> {
'data-testid'?: string;
'data-cy'?: string; // Cypress测试
}

// 扩展CSS属性
interface CSSProperties {
'--custom-color'?: string;
'--theme-primary'?: string;
}

// 添加自定义Hook类型
interface ComponentProps<T = {}> {
className?: string;
'data-testid'?: string;
style?: CSSProperties;
}
}

// 使用扩展的React属性
const MyComponent: React.FC = () => {
return (
<div
data-testid="my-component"
style={{ '--custom-color': '#ff0000' }}
>
Content
</div>
);
};

// 5. Node.js环境扩展
declare module 'process' {
global {
namespace NodeJS {
interface ProcessEnv {
// 明确定义环境变量类型
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
JWT_SECRET: string;
REDIS_URL?: string;
PORT?: string;
LOG_LEVEL?: 'debug' | 'info' | 'warn' | 'error';
}

interface Process {
// 添加自定义进程方法
gracefulShutdown(): Promise<void>;
isDevelopment(): boolean;
isProduction(): boolean;
}
}
}
}

// 实现自定义进程方法
process.gracefulShutdown = async () => {
console.log('Starting graceful shutdown...');
// 关闭数据库连接、清理资源等
process.exit(0);
};

process.isDevelopment = () => process.env.NODE_ENV === 'development';
process.isProduction = () => process.env.NODE_ENV === 'production';

// 6. 库的命名空间扩展
declare namespace jQuery {
interface JQuery {
// 添加自定义jQuery插件
customSlider(options?: {
speed?: number;
autoplay?: boolean;
dots?: boolean;
}): JQuery;

validation(rules?: Record<string, any>): JQuery;
}

// 扩展静态方法
interface JQueryStatic {
customUtility<T>(data: T[]): T[];
}
}

// 7. CSS-in-JS库扩展
declare module 'styled-components' {
// 扩展主题类型
interface DefaultTheme {
colors: {
primary: string;
secondary: string;
background: string;
text: string;
};
fonts: {
primary: string;
secondary: string;
};
breakpoints: {
mobile: string;
tablet: string;
desktop: string;
};
}
}

// 使用扩展的主题类型
import styled from 'styled-components';

const Button = styled.button`
background: ${props => props.theme.colors.primary}; // ✅ 类型安全
font-family: ${props => props.theme.fonts.primary};

@media (min-width: ${props => props.theme.breakpoints.tablet}) {
padding: 12px 24px;
}
`;

// 8. 测试框架扩展
declare namespace jest {
interface Matchers<R> {
// 自定义Jest匹配器
toBeValidEmail(): R;
toBeInRange(min: number, max: number): R;
toHaveLength(length: number): R;
}
}

// 实现自定义匹配器
expect.extend({
toBeValidEmail(received: string) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const pass = emailRegex.test(received);

return {
pass,
message: () => `expected ${received} ${pass ? 'not ' : ''}to be a valid email`
};
}
});

// 使用自定义匹配器
test('email validation', () => {
expect('user@example.com').toBeValidEmail(); // ✅ 类型安全
});

// 9. 数据库ORM扩展
declare module 'sequelize' {
interface Model {
// 添加自定义实例方法
toSafeJSON(): Record<string, any>;
audit(userId: string, action: string): Promise<void>;
}

interface ModelStatic {
// 添加自定义静态方法
findByEmail(email: string): Promise<Model | null>;
softDelete(id: string): Promise<number>;
}
}

// 10. 错误对象扩展
declare global {
interface Error {
code?: string;
statusCode?: number;
isOperational?: boolean;
}
}

// 创建增强的错误类
class AppError extends Error {
public statusCode: number;
public isOperational: boolean;

constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;

Error.captureStackTrace(this, this.constructor);
}
}

// 11. 日期库扩展
declare module 'dayjs' {
interface Dayjs {
// 添加自定义方法
toBusinessDay(): Dayjs;
isWeekend(): boolean;
quarter(): number;
}
}

// 12. HTTP客户端扩展
declare module 'axios' {
interface AxiosRequestConfig {
// 添加自定义配置
retry?: number;
retryDelay?: number;
skipAuth?: boolean;
}

interface AxiosResponse<T = any> {
// 扩展响应对象
cached?: boolean;
fromServiceWorker?: boolean;
}
}

// 13. 状态管理库扩展
declare module 'redux' {
interface Action {
// 扩展Action接口
meta?: {
timestamp: number;
source: string;
};
error?: boolean;
}
}

// 14. 路由库扩展
declare module 'react-router-dom' {
interface RouteComponentProps {
// 添加自定义路由属性
user?: User;
permissions?: string[];
}
}

// 15. 实用的扩展模式
// types/extensions.d.ts - 集中管理扩展
export {}; // 确保这是一个模块

declare global {
// 扩展内置对象
interface Object {
isEmpty(): boolean;
}

interface Date {
addDays(days: number): Date;
addMonths(months: number): Date;
format(format: string): string;
}

// 扩展Promise
interface Promise<T> {
timeout(ms: number): Promise<T>;
retry(attempts: number): Promise<T>;
}
}

// 实现扩展
Object.prototype.isEmpty = function() {
return Object.keys(this).length === 0;
};

Date.prototype.addDays = function(days: number): Date {
const result = new Date(this);
result.setDate(result.getDate() + days);
return result;
};

// 16. 类型安全的扩展模式
interface ExtendedArray<T> extends Array<T> {
first(): T | undefined;
last(): T | undefined;
random(): T | undefined;
}

function createExtendedArray<T>(items: T[]): ExtendedArray<T> {
const arr = [...items] as ExtendedArray<T>;

arr.first = function() { return this[0]; };
arr.last = function() { return this[this.length - 1]; };
arr.random = function() {
return this[Math.floor(Math.random() * this.length)];
};

return arr;
}

const numbers = createExtendedArray([1, 2, 3, 4, 5]);
const first = numbers.first(); // number | undefined - 类型安全

面试官视角:

模块扩展体现了TypeScript的灵活性和渐进式特性:

  • 要点清单: 理解模块扩展语法;掌握全局接口扩展;了解第三方库扩展技巧;能安全地扩展内置对象
  • 加分项: 掌握复杂的扩展模式;理解扩展的性能影响;能设计类型安全的扩展API
  • 常见失误: 扩展语法错误;全局污染;扩展作用域理解错误;类型安全性忽视

延伸阅读: