装饰器
// 装饰器
// 只能在类中使用(类本身,类成员使用)
// 装饰器,类的装饰器,属性装饰器,访问装饰器 参数装饰器
// 1. 类型装饰器 给类进行扩展,也可以返回一个子类
// 先要在tsconfig.json中开启experimentalDecorators
const classDecorator1 = <T extends new (...args: any[]) => any>(target: T) => {
(target as any).prototype.name = 'hello';
(target as any).prototype.say = function () {
console.log('hello');
};
Object.assign(target.prototype, {
name: 'hello',
drink() {},
eat() {
console.log('hello');
},
});
};
const classDecorator2 = <T extends new (...args: any[]) => any>(target: T) => {
// 基于父类返回一个子类
return class extends target {
name = 'hello';
};
};
@classDecorator1
class Animal {}
@classDecorator2
class Person {}
// 2. 方法装饰器
function Enum(isEnum: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// descriptor.value = isEnum ? 'enum' : 'not enum';
descriptor.enumerable = isEnum;
};
}
export {};
装饰器的运行流程
使用它时要开启experimentalDecorators: true
, 装饰器就是一个个函数,用来对类和成员进行操作
现在我们已经:
启用了 experimentalDecorators 支持
启用了 emitDecoratorMetadata 支持
设置了目标为 ES5(装饰器在这个版本上工作得最好)
这样配置后,IDE 中的波浪线警告应该会消失。装饰器现在可以正确地用于:
类装饰器(@Echo('类的装饰器'))
方法装饰器(@Echo('原型方法装饰'))
参数装饰器(@Echo('参数装饰'))
如果你仍然看到波浪线,可能需要重启 IDE 或重新加载 TypeScript 服务器。
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2025-03-22 13:42:13
* @LastEditors: Walker zw37520@gmail.com
* @LastEditTime: 2025-03-22 14:22:18
* @FilePath: /ts-classes/src/index.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
function Echo(val: string) {
return function (
target: any,
propertyKey?: string | symbol,
descriptorOrIndex?: PropertyDescriptor | number
): any {
if (typeof descriptorOrIndex === 'number') {
// 参数装饰器
console.log('参数装饰器:', val, target, propertyKey, descriptorOrIndex);
} else if (descriptorOrIndex instanceof Object) {
// 方法装饰器
console.log('方法装饰器:', val, target, propertyKey, descriptorOrIndex);
} else {
// 类装饰器
console.log('类装饰器:', val, target);
}
};
}
@Echo('类的装饰器4')
@Echo('类的装饰器3')
@Echo('类的装饰器2')
@Echo('类的装饰器1')
class Flow {
// 装饰器是下往上执行
constructor(@Echo('构造器参数装饰') val: string) {
console.log('构造器');
}
@Echo('原型方法装饰')
handle(@Echo('原型方法参数装饰') val: string) {
console.log('方法');
}
@Echo('静态属性装饰器')
static age: number = 18;
@Echo('静态方法装饰')
static handle(@Echo('静态方法参数装饰') val: string) {
console.log('静态方法');
}
@Echo('实例属性装饰器')
name: string = '张三';
@Echo('实例原型方法')
handler(@Echo('实例原型方法参数装饰') val: string) {
console.log('实例原型方法');
}
}
// 【实例属性、方法、属性访问,定义在前端的先执行】 【静态属性、方法、属性访问,定义在前面的先执行】 【类装饰器】
// 本质 就是一个函数对内容不停的包裹,洋葱模型
// 装饰器一般会搭配反射来使用
// 什么是元数据(描述数据的数据)
// 装饰器是元数据的一种实现
// 反射 需要第三方库来使用reflect-metadata
export {};
容器的依赖注入(ioc-di)
// 控制反转与依赖注入
// 什么是控制反转
// 控制反转(Inversion of Control,IoC)是一种设计原则,用于减少类之间的耦合度。它将对象的创建和依赖关系的管理从类内部转移到外部容器或框架中。
// 控制反转的目的是:
// 1. 降低类之间的耦合度
// 2. 提高代码的可维护性和可扩展性
// 3. 实现松耦合的模块化设计
interface IMonitor {
display(): void;
}
interface IHost {
run(): void;
}
class Monitor27Inch implements IMonitor {
display() {
console.log('27寸显示器');
}
}
class AppleHost implements IHost {
run() {
console.log('苹果主机');
}
}
// class Computer {
// public monitor: IMonitor;
// public host: IHost;
// constructor(monitor: IMonitor, host: IHost) {
// this.monitor = monitor;
// this.host = host;
// }
// start() {
// // 启动
// this.monitor.display();
// this.host.run();
// }
// }
// const computer = new Computer(new Monitor27Inch(), new AppleHost());
// computer.start(); // 控制正转 都写死了
class Computer {
constructor(public monitor: IMonitor, public host: IHost) {}
start() {
this.monitor.display();
this.host.run();
}
}
const computer = new Computer(new Monitor27Inch(), new AppleHost());
computer.start(); // 如果有好多参数就会很麻烦了
// 定义一个容器 来管理对象的创建
class Container {
private binds: Map<string, any> = new Map(); // 存储创建的实例
private properties: Map<string, any> = new Map(); // 存储注入的属性
bind<T>(key: string, creater: () => T) {
if (!this.binds.has(key)) {
this.binds.set(key, creater());
}
}
get<T>(key: string): T {
// 将记录的属性自动的注入到当前的实例上
let instance = this.binds.get(key);
let properties = this.properties.get(key);
if (properties) {
properties.forEach((property) => {
instance[property] = this.get(property);
});
}
return instance;
}
}
const container = new Container();
container.bind('monitor', () => new Monitor27Inch());
container.bind('host', () => new AppleHost());
container.bind(
'computer',
() => new Computer(container.get('monitor'), container.get('host'))
);
computer.start();
// 注解的写法
interface IMonitor {
display(): void;
}
interface IHost {
run(): void;
}
// 提供到容器中,自动会创建实例在容器中
@Provide('Monitor')
class Montior27inch implements IMonitor {
display() {
console.log('27寸显示器');
}
}
@Provide('Host')
class AppleHost implements IHost {
run() {
console.log('苹果主机');
}
}
function Provide(key: string) {
return (target: any) => {
container.bind(key, () => new target());
};
}
function Inject(key: string) {
return (target: any, propertyKey: string) => {
// 当前哪个原型上注入了哪些属性,做一个映射关系,稍后解析电脑的时候自动解释它所依赖的属性
// container.bind(propertyKey, () => container.get(key));
container.properties.set(
`${target.prototype.constructor.name}.${propertyKey}`,
key
);
// 关联就是哪个类,对应哪个属性,用哪个哪个标识找到实例来进行赋值
};
}
// DI 依赖注入, 不需要在类中硬编码
@Provide('Computer')
class Computer {
// 注入进来
@Inject('Monitor')
monitor: IMonitor;
@Inject('Host')
host: IHost;
start() {
this.monitor.display();
}
}
export {};
模拟一个nestjs的路由
// nestjs 的依赖注入 @Controller('articles'/add)
// articles 是路由,add 是方法
import 'reflect-metadata';
// 定义装饰器工厂函数
function methodDecorator(method: string) {
return function (path: string) {
return function (target: any, propertyKey: string, descriptor: any) {
Reflect.defineMetadata('method', method, descriptor.value);
Reflect.defineMetadata('path', path, descriptor.value);
};
};
}
// 定义装饰器
export const Post = methodDecorator('post');
export const Get = methodDecorator('get');
export const Delete = methodDecorator('delete');
// 类装饰器
function Controller(path: string = '') {
return (target: any) => {
Reflect.defineMetadata('path', path, target); // 给类添加了一个元数据
};
}
// 使用装饰器
@Controller('/articles')
class ArticlesController {
@Post('/add')
addArticle() {
console.log('add article');
}
@Get('/detail')
getArticle() {
console.log('get article');
}
@Delete('/remove')
removeArticle() {
console.log('remove article');
}
}
// 最终生成一个路由 /articles/add 和 /articles/detail 和 /articles/remove 然后触发对应的逻辑
const controller = new ArticlesController();
function createRoutes(instance: any) {
const protops: any = Reflect.getPrototypeOf(instance) || {};
const classPath = Reflect.getMetadata('path', protops.constructor);
let keys = Reflect.ownKeys(protops!).filter((key) => key !== 'constructor');
let routes: any[] = [];
keys.forEach((key) => {
const path = Reflect.getMetadata('path', protops[key]);
const method = Reflect.getMetadata('method', protops[key]);
console.log(path, method);
routes.push({
path: `${classPath}${path}`,
method,
handler: protops[key],
});
});
return routes;
}
const routes = createRoutes(controller);
console.log(routes);
export {};
类型保护
// 类型保护
// 通过判断,去识别类型、核心就是进行类型的收窄
function doubel(val: number | string) {
if (typeof val === 'number') {
return val * 2;
} else {
return val + val;
}
}
class Person {}
class Dog {}
function getInstance(clazz: new () => Dog | Person) {
return new clazz();
}
const instance = getInstance(Person);
if (instance instanceof Person) {
console.log('instance is a Person');
} else {
console.log('instance is a Dog');
}
interface Bird {
kind: 'bird';
fly: string;
}
interface Fish {
kind: 'fish';
swim: string;
}
// 类型谓词 自定义的类型保护
function isBird(animal: Bird | Fish): animal is Bird {
return animal.kind === 'bird';
}
function getAnimal(animal: Bird | Fish) {
if (isBird(animal)) {
return animal.fly;
} else {
return animal.swim;
}
}
function ensureArray<T>(value: T | T[]): T[] {
if (Array.isArray(value)) {
return value;
}
return [value];
}
export {};
模板字符串
// 模板字符串类型 类似于js中的es6的模板字符串
// 可以将多个字符串类型进行组装
type name = 'jiangwen';
type age = 30;
type sayName = `handsome ${name} ${age}`; // 就是es6的模板字符,但它产生的是一个类型
// 模板字符串也是具备分发能力的
type Direction = 'top' | 'bottom' | 'right' | 'left';
type AllMargin = `margin-${Direction}`;
type AllPadding = `padding-${Direction}`;
// 也可以使用多对多
// 购物 sku 1.0 2.0 3.0 20.0
type Sku = '1.0' | '2.0' | '3.0';
type Sku2 = 20 | 30 | 40;
type IRL = `${Sku}-${Sku2}`;
// 放到字符串内的东西,需要结束,必须得能转化成字符串
type sayHello<T extends string | boolean | null | undefined | number | bigint> =
`hello, ${T}`;
type R1 = sayHello<'jiang'>;
type R2 = sayHello<1>;
type R3 = sayHello<number>;
let val: R3 = 'hello 333';
type IFlog = R2 extends R3 ? true : false; // 所有的基础类型的模板字符串都是字面量类型的父类型(类型相同)
// 交对象的属性进行重新命名 {name,age,address} -> {r_name,r_age,r_address}
type Person = {
name: string;
age: number;
address: string;
};
type Rename<T> = {
[K in keyof T as `r_${string & K}`]: T[K];
};
type R11 = Rename<Person>;
let person: Person = {
name: 'jiangwen',
age: 30,
address: 'beijing',
};
// 字符串可以支持工具类型 UpperCase 大写 LowerCase 小写 Capitalize 首字母大写 Uncapitalize 首字母小写
type WithGetter<T> = {
[K in keyof T as `get${string & Capitalize<K>}`]: () => T[K];
};
let personGetter: WithGetter<Person> = {
getName: () => {
return person.name;
},
getAge: () => {
return person.age;
},
getAddress: () => {
return person.address;
},
};
type PersonGetter = typeof personGetter;
// 根据模式匹配符来取类型 jinag wen
// infer 可以进行位置推断
type GetNameFirstChar<T> = T extends `${infer First}${infer Rest}`
? First
: never;
type R12 = GetNameFirstChar<'jiangwen'>;
//
export {};
模块和命名空间
// 模块和命名空间
// 模块叫外部模块 命名空间叫内部模块
// 目前我们主要采用es6的模块来创建作用域(按照文件来创建作用域) 当我们使用import 和 export 来导入和导出模块时,ts会自动帮我们进行类型推断 将其变成一个模块
// 模块的导出和导入
// 1. 导出
// 2. 导入
// 3. 导出和导入
// 4. 导出和导入
// 打包后会有一些常见的模块规范 esm(es6 module) commonjs 和 umd(universal module definition) amd(asynchronous module definition)
// commonjs 是nodejs的模块规范 它使用require来导入模块 使用module.exports来导出模块
// esm 是es6的模块规范 它使用import来导入模块 使用export来导出模块
// umd 是通用模块定义 它可以在多种环境下使用 包括浏览器和nodejs
// amd 是异步模块定义 它使用define来定义模块 使用require来导入模块
// commonjs 不能转换为 esm 需要使用babel来转换
// commonjs 不能转换为amd 需要使用babel来转换
// 模块不会混用,es6下引用commonjs的模块引用
//在ts语法中有一种模块化方式 (export = | import xx = )
export {};
类型声明
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://joyjs.cn/archives/4411