泛型、装饰器、interface 与 type、命名空间
按泛型 → 装饰器 → interface/type → 命名空间的常见顺序编排;各篇可独立阅读。
第一部分:泛型
泛型(Generics)是 TS 中用于创建可复用、类型安全的组件的核心特性
它的核心思想是:在定义函数、类、接口时,不预先指定具体类型,而是在使用时再确定类型。
可以把泛型理解为「类型的占位符 / 变量」—— 就像函数的参数接收值,泛型参数接收「类型」
泛型函数(最常用)
/********************* 单泛型 ***********************/
// 定义泛型函数
function returnValue<T>(arg: T): T {
return arg;
}
// 使用时指定类型(显式)
const num = returnValue<number>(10); // num 类型是 number
const str = returnValue<string>('hello'); // str 类型是 string
// 使用时自动推导类型(隐式,更常用)
const bool = returnValue(true); // bool 类型是 boolean
const arr = returnValue([1, 2, 3]); // arr 类型是 number[]
/********************* 多泛型 ***********************/
// 合并两个对象:T 是第一个对象类型,U 是第二个对象类型
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const user = merge({ name: '张三' }, { age: 20 });
// user 类型是 { name: string } & { age: number }
console.log(user.name, user.age); // 类型提示正常泛型接口
// 定义泛型接口:表示“键值对”结构,键是 string,值是泛型 T
interface KeyValue<T> {
key: string;
value: T;
}
// 使用时指定类型
const numKV: KeyValue<number> = { key: 'age', value: 20 };
const strKV: KeyValue<string> = { key: 'name', value: '张三' };泛型类
// 泛型类:栈(先进后出)
class Stack<T> {
private items: T[] = [];
// 入栈
push(item: T): void {
this.items.push(item);
}
// 出栈
pop(): T | undefined {
return this.items.pop();
}
// 获取栈长度
size(): number {
return this.items.length;
}
}
// 使用数字类型的栈
const numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
console.log(numStack.pop()); // 2(类型是 number | undefined)
// 使用字符串类型的栈
const strStack = new Stack<string>();
strStack.push('a');
strStack.push('b');
console.log(strStack.pop()); // 'b'(类型是 string | undefined)泛型约束 extends | extends keyof
默认情况下,泛型 T 可以是任意类型,但有时我们需要限制 T 的范围(比如要求 T 必须包含某个属性),这就是「泛型约束」。
/******************** 示例一:要求参数必须有 length 属性 *************************/
// 定义约束接口:包含 length 属性
interface HasLength {
length: number;
}
// 泛型 T 继承 HasLength,限制 T 必须有 length 属性
function getLength<T extends HasLength>(arg: T): number {
return arg.length;
}
// 合法:string 有 length 属性
console.log(getLength('hello')); // 5
// 合法:数组有 length 属性
console.log(getLength([1, 2, 3])); // 3
// 不合法:number 没有 length 属性(TS 会报错)
// console.log(getLength(123));
/******************** 示例二:约束泛型为对象的键 *************************/
// 获取对象的属性值:K 必须是 T 的键(keyof T 表示 T 的所有键的联合类型)
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: '张三', age: 20 };
// 合法:"name" 是 user 的键
console.log(getProperty(user, 'name')); // 张三(类型是 string)
// 合法:"age" 是 user 的键
console.log(getProperty(user, 'age')); // 20(类型是 number)
// 不合法:"gender" 不是 user 的键(TS 报错)
// console.log(getProperty(user, "gender"));泛型默认值
// 泛型默认值:T 默认为 string
function createArray<T = string>(length: number, value: T): T[] {
return new Array(length).fill(value);
}
// 不指定类型,T 用默认值 string
const strArr = createArray(3, 'a'); // strArr 类型是 string[]
// 指定类型,覆盖默认值
const numArr = createArray<number>(3, 1); // numArr 类型是 number[]泛型工具类型(TS 内置)
工具类型 作用 示例
Partial<T> 将 T 的所有属性变为可选 Partial<{ name: string }> → { name?: string }
Required<T> 将 T 的所有属性变为必填 Required<{ name?: string }> → { name: string }
Readonly<T> 将 T 的所有属性变为只读 Readonly<{ name: string }> → { readonly name: string }
Pick<T, K> 从 T 中挑选 K 个属性 Pick<{ name: string, age: number }, "name"> → { name: string }
Omit<T, K> 从 T 中排除 K 个属性 Omit<{ name: string, age: number }, "age"> → { name: string }
Record<K, T> 创建键为 K、值为 T 的对象类型 Record<"name" | "age", string> → { name: string; age: string }// 示例:使用 Partial 简化类型
interface User {
name: string;
age: number;
}
// 需求:更新用户信息,允许只传部分属性
type PartialUser = Partial<User>;
// PartialUser 等价于 { name?: string; age?: number }
function updateUser(user: User, changes: PartialUser): User {
return { ...user, ...changes };
}
const oldUser = { name: '张三', age: 20 };
const newUser = updateUser(oldUser, { age: 21 }); // 只更新 age第二部分:装饰器
使用装饰器可以让我们实现 AOP 编程(面向切面编程)(拦截器思维)、可以提升架构思维和架构能力、Nest、mideway 知名框架大量使用了装饰器
定义:装饰器就是一个方法,可以注入到类、方法、属性、参数上,扩展其功能
初始化 装饰器 环境
1. tsc --init
2. 修改 tsconfig.json 配置文件
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
3. yarn global add ts-node
4. 新建 index.ts示例
ts-node index.ts- 不传参数
结果:装饰器函数会执行,且默认把类作为参数传入装饰器工厂函数
// 不传参
function FirstDecorator(Target: any) {
console.log("I am decorator");
console.log("====================================");
const target = new Target();
target.read();
}
@FirstDecorator
class Person {
name = "zhangsan";
read() {
console.log("i am class method");
console.log("====================================");
}
}- 传参数
结果:在不传参数的基础上,可以给装饰器函数传入参数
// 传参
function FirstDecorator(param: string) {
console.log("I am param", param);
console.log("====================================");
return function (Target: any) {
console.log("I am decorator");
console.log("====================================");
const target = new Target();
target.read();
};
}
@FirstDecorator("hello world")
class Person {
name = "zhangsan";
read() {
console.log("i am class method");
console.log("====================================");
}
}装饰器分类
类装饰器、属性装饰器、方法装饰器、访问器装饰器、参数装饰器
装饰器的执行顺序:属性装饰器 -- 方法参数装饰器 -- 方法装饰器 -- 构造器参数装饰器 -- 类装饰器
多个类装饰器的执行顺序是:倒着执行,从下往上执行
// 类装饰器
@classDecorator
class Bird {
// 属性装饰器
@propertyDecorator
name: string;
// 方法装饰器
@methodDecorator
fly(
// 参数装饰器
@parameterDecorator
meters: number
) {}
// 访问器装饰器
@accessorDecorator
get egg() {}
}第三部分:interface 与 type
在 TypeScript 中,type 和 interface 都用于定义自定义类型,但它们有一些关键区别和各自适用的场景。
interface 接口
专门用于定义对象 / 类的形状,语法更面向对象,只能定义对象类型
// 定义对象接口
interface Person {
name: string;
age: number;
sayHi?: () => void; // 可选属性
}
// 接口继承(支持多继承)
interface Student extends Person {
studentId: string;
}
// 接口合并(同名接口会自动合并)
interface Person {
gender: string; // 合并后 Person 包含 name/age/sayHi/gender
}type 类型别名
可定义任意类型(对象、基本类型、联合类型、交叉类型等),语法更灵活
// 定义基本类型别名
type Age = number;
// 定义对象类型
type Person = {
name: string;
age: number;
sayHi?: () => void;
};
// 定义联合类型(interface 无法实现)
type Status = "success" | "error" | "pending";
// 定义交叉类型
type Student = Person & { studentId: string };
// 类型别名不可合并(同名会报错)
// type Person = { gender: string }; // 报错:标识符“Person”重复二者区别
特性 interface type
定义范围 仅支持对象 / 类类型 支持所有类型(基本、联合、交叉等)
扩展方式 通过 extends 继承 通过 & 交叉类型实现
类实现 类可通过 implements 实现接口 不能
合并声明 同名自动合并 不支持合并,同名会报错
应用类型 只能用于对象类型 定义任意类型(联合、元组等)使用建议
- 定义对象 / 类的结构,且可能需要扩展 / 合并 → 优先用 interface;
- 定义基本类型、联合类型、交叉类型、元组 → 必须用 type;
- 团队协作时,保持风格统一(比如 React 组件 Props 统一用 interface)。
总结
- interface 专注于对象 / 类的形状定义,支持继承和声明合并,更贴合面向对象编程;
- type 是通用的类型别名,支持所有类型定义,功能更灵活但不支持合并;
- 核心选择逻辑:对象结构优先 interface,非对象类型(联合 / 交叉 / 基本类型)必须用 type。
第四部分:命名空间
在 TypeScript 中,命名空间(Namespace)是一种组织代码的方式,用于将相关的接口、类、函数等封装在一起,避免全局作用域中的命名冲突。
它类似于模块,但在使用方式和场景上有一定区别(命名空间更偏向于在全局作用域中组织代码,而模块更适合文件级别的代码分离)
基础用法
命名空间使用 namespace 关键字定义,内部可以包含变量、函数、类、接口等:
命名空间内部的成员默认是 “私有” 的,仅在命名空间内部可见。若要让外部访问,必须使用 export 关键字导出
// 定义命名空间
namespace MyNamespace {
export const message = "Hello from namespace"; // 需要用 export 暴露外部可访问的成员
export function logMessage() {
console.log(message);
}
export class MyClass {
greet() {
console.log("Greetings!");
}
}
// 未使用 export 的成员仅在命名空间内部可见
const internalVar = "内部变量";
}访问命名空间成员
通过 命名空间.成员名 的方式访问导出的成员
// 访问命名空间中的成员
console.log(MyNamespace.message); // 输出:Hello from namespace
MyNamespace.logMessage(); // 输出:Hello from namespace
const instance = new MyNamespace.MyClass();
instance.greet(); // 输出:Greetings!嵌套命名空间
命名空间可以嵌套,形成更细粒度的代码组织
namespace OuterNamespace {
export namespace InnerNamespace {
export function hello() {
console.log("Hello from inner namespace");
}
}
}
// 访问嵌套命名空间的成员
OuterNamespace.InnerNamespace.hello(); // 输出:Hello from inner namespace总结
命名空间用于组织相关代码,避免命名冲突。
内部成员需用 export 导出才能被外部访问。
支持嵌套和合并。
现代项目中更推荐使用模块,命名空间适用于简单场景或兼容旧代码。
