何以解忧
何以解忧
发布于 2025-08-17 / 15 阅读
0
0

TypeScript 5 装饰器 - stage3提案

配置/说明

typescript版本:5.9.2

tsconfig.json配置文件

{
  "compilerOptions": {
    "experimentalDecorators": false,    // ❌ 关闭 TypeScript 旧版实验装饰器支持(TS5 新装饰器不依赖这个)
    "emitDecoratorMetadata": false,     // ❌ 关闭旧的装饰器元数据生成(TS5 新装饰器不需要)
    "declaration": false,               // ❌ 不生成 `.d.ts` 类型声明文件,减少构建产物
    "target": "ESNext",                 // ✅ 编译输出的 JavaScript 版本,这里选择 ESNext 保留最新语法特性(例如 class fields、private fields 等)
    "module": "ESNext",                 // ✅ 输出模块类型为 ES 模块(import/export 语法),适合现代 JS 环境或打包工具
    "useDefineForClassFields": true,    // ✅ 使用 ECMAScript 标准语义定义类字段,保证类字段初始化和 TS5 装饰器兼容
    "strict": true,                     // ✅ 启用严格模式,包含所有严格类型检查选项,提高代码安全性
    "esModuleInterop": true,            // ✅ 允许在 CommonJS 模块中默认导入 ES 模块,方便兼容第三方库
    "removeComments": true,             // ✅ 编译时移除注释,减小输出文件体积
    "allowSyntheticDefaultImports": true, // ✅ 允许默认导入没有 default export 的模块,配合 esModuleInterop 使用
    "sourceMap": true,                  // ✅ 生成 source map 文件,便于调试 TypeScript 源码
    "outDir": "dist",                   // ✅ 编译输出目录,编译后的 JS 文件将放在 dist 文件夹
    "baseUrl": "./",                    // ✅ 解析非相对模块导入的基准路径,通常配合 paths 使用
    "skipLibCheck": true                // ✅ 跳过第三方库的类型检查,减少大型项目的编译时间
  },
  "include": [
    "src/**/*"                          // ✅ 指定要编译的文件或文件夹,这里是 src 下所有文件
  ],
  "exclude": [
    "node_modules"                      // ✅ 排除不需要编译的文件夹(通常是依赖库)
  ]
}

context类型

type DecoratorContext =
    | ClassDecoratorContext        // 类装饰器上下文
    | ClassMethodDecoratorContext  // 方法装饰器上下文
    | ClassGetterDecoratorContext  // getter 装饰器上下文
    | ClassSetterDecoratorContext  // setter 装饰器上下文
    | ClassFieldDecoratorContext   // 字段装饰器上下文
    | ClassAccessorDecoratorContext// accessor 装饰器上下文

装饰器工厂

//返回一个装饰器
const decoratorFactory = () => {
    console.log("类工厂=>返回一个装饰器")
    return (target: any, context: ClassDecoratorContext) => {
        //...
    }
}

类装饰器

//类装饰器
const classDecorator = (target: any, context: ClassDecoratorContext) => {
    console.log("类装饰器:原型注入/修改")
    target.prototype.__name = "张三" //原型注入[__name]属性

    //原型注入 [getTheName] 方法
    target.prototype.getTheName = function () {
        return `你好,我是:${this.__name}`;
    }
}

@classDecorator
class MyClass {
}

const myClass = new MyClass();
console.log(myClass.__name); //张三
console.log(myClass.getTheName()); //你好,我是:张三

方法装饰器

function bound(originalMethod: Function, context: ClassMethodDecoratorContext) {
    if (context.private) throw new Error("不支持私有方法");
    //加载完成时 修改注入新方法
    context.addInitializer(function (this: any,) {
        //直接拷贝绑定某方法到newCopy
        this["newCopy"] = this[context.name].bind(this);
        //修改原方法到newGreet
        this["newGreet"] = (...args: any[]) => {
            const result = originalMethod.call(this, ...args);
            return `巴拉巴拉--${result}`;
        }
    });
    //修改原方法
    return function (this: any, ...args: any[]) {
        const result = originalMethod.call(this, ...args);
        return `你好--${result}`;
    };
}

class C {
    message = "Hello";

    @bound
    greet() {
        return this.message;
    }
}

const c = new C();
console.log(c.greet())//你好--Hello
console.log(c.newCopy());//你好--Hello
console.log(c.newGreet())//巴拉巴拉--Hello

属性装饰器

//属性装饰器示例1
const minAge = (min: number) => {//指定最小年龄
    console.log("属性工厂:创建minAge装饰器")
    return function (target: any, context: ClassFieldDecoratorContext) {
        console.log("属性装饰器:minAge => 拦截年龄小于18的复制的赋值")
        context.addInitializer(function (this: any) {
            //赋予初始值
            //this[context.name]=66

            //重写get set => 拦截18岁以下

            // 保存初始值
            let value = this[context.name];
            Object.defineProperty(this, context.name, {
                configurable: true,
                enumerable: true,
                get() {
                    return value;
                },
                set(newValue: number) {
                    if (newValue < min) {
                        throw new Error(`[${String(context.name)}] 年龄(${newValue})必须 >= ${min}`);
                    }
                    value = newValue;
                }
            });
        });
    };
}

class MyClass {
    @minAge(18) age: number = 18;
}

const p: any = new MyClass();

p.age = 20
console.log(p.age)//20

try {
    p.age = 17
} catch (e) {
    console.log("赋值失败:", e.message);//[age] 年龄(17)必须 >= 18
}
console.log(p.age)//20






//属性装饰器示例2
const minLength = (minLength: number) => {//指定最小名称长度
    return (target: any, context: ClassAccessorDecoratorContext) => {
        //自定义access
        return {
            set(this: any, value: string) {
                if (value.length < minLength) {
                    throw new Error(`[${String(context.name)}] 长度(${value.length})必须 >=${minLength}`);
                }
                return target.set.call(this, value.toUpperCase());
            },
            get(this: any) {
                return "我的名字是:" + target.get.call(this);
            }
        }
    }
}

const minAge = (min: number) => {//指定最小年龄
    return (target: any, context: ClassFieldDecoratorContext)=> {
        context.addInitializer(function (this: any) {
            //赋予初始值
            //this[context.name]=66

            //重写get set => 拦截18岁以下

            // 保存初始值
            let value = this[context.name];
            Object.defineProperty(this, context.name, {
                configurable: true,
                enumerable: true,
                get() {
                    return value;
                },
                set(newValue: number) {
                    if (newValue < min) {
                        throw new Error(`[${String(context.name)}] 年龄(${newValue})必须 >=${min}`);
                    }
                    value = newValue;
                }
            });
        });
    };
}


class User {
    @minLength(3) accessor name: string | undefined
    @minAge(18) age: number | undefined;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const user = new User("李元芳", 18);

console.log(user.age);// 18
try {
    user.age = 16;
} catch (e) {
    console.log(e.message);// [age] 年龄(16)必须 >=18
}
console.log(user.age);// 18


console.log(user.name);// 我的名字是:李元芳
try {
    user.name = "张三";
} catch (e) {
    console.log(e.message);// [name] 长度(2)必须 >=3
}
console.log(user.name);// 我的名字是:李元芳


装饰器-混合使用Demo

/**
 * ts5 新提案装饰器
 */

const d1 = (target: any, context: any) => {
    console.log("类装饰器:1")
}
const d2 = () => {
    console.log("类工厂:2")
    return (target: any, context: any) => {
        console.log("类装饰器:2")
    }
}
const d3 = () => {
    console.log("类工厂:3")
    return (target: any, context: any) => {
        console.log("类装饰器:3")
    }
}
const d4 = (target: any, context: any) => {
    console.log("类装饰器:4")
}


//类装饰器
const classDecorator = (target: any, context: any) => {
    console.log("类装饰器:原型注入/修改")
    target.prototype.__name = "other name" //原型注入[__name]属性

    //原型注入 [getTheName] 方法
    target.prototype.getTheName = function () {
        console.log("[getTheName] => ", this.__name);
        return this.__name;
    }

    //原型注入 [copyMethod1] 方法 => 拷贝 MyClass[method1] 原型方法
    target.prototype.copyMethod1 = function () {
        return this.method1.call(this); //拷贝 => MyClass[method1] 方法
    }
}

//方法装饰器
const methodDecorator = (originalMethod: any, context: any) => {
    console.log("方法装饰器:注入=>方法执行前打印")
    return function (this: any, ...args: any[]) {
        console.log("\n[准备执行方法] =>", context.name);
        const result = originalMethod.call(this, ...args);
        console.log("[执行完毕] =>", context.name, "\n");
        return result;
    };
}

//属性装饰器
const minAge = (min: number) => {
    console.log("属性工厂:创建minAge装饰器")
    return function (target: any, context: ClassFieldDecoratorContext) {
        console.log("属性装饰器:minAge => 拦截年龄小于18的复制的赋值")
        context.addInitializer(function (this: any) {
            //赋予初始值
            //this[context.name]=66

            //重写get set => 拦截18岁以下

            // 保存初始值
            let value = this[context.name];
            Object.defineProperty(this, context.name, {
                configurable: true,
                enumerable: true,
                get() {
                    return value;
                },
                set(newValue: number) {
                    if (newValue < min) {
                        throw new Error(`${String(context.name)} 必须 >= ${min}, got ${newValue}`);
                    }
                    value = newValue;
                }
            });
        });
    };
}

console.log("\n----装饰器顺序----")

//先顺序执行工厂取装饰器 => (属性装饰器)  =>后倒序执行装饰器
@classDecorator
@d1
@d2()
@d3()
@d4
class MyClass {
    //__name:string 由装饰器注入
    //getTheName:()=>void 由装饰器注入
    //copyMethod1:()=>void 由装饰器注入

    name: string;
    @minAge(18) age: number;

    //构造器
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
        this.temp = this.method1.bind(this); //拷贝this.method1方法=>this.temp
    }

    @methodDecorator
    //默认方法1
    method1() {
        console.log(`[method1] => Hi,我是 ${this.name}.`);
    }

    //默认方法 打印年龄
    logAge() {
        console.log(`[logAge] => 我的年龄是 ${this.age}.`);
    }

    //默认空方法 由构造器创建
    temp: () => void;

    setAge(newAge: number) {
        try {
            this.age = newAge;// ❌ 不可以赋值=>未正常拦截
            console.log(`赋值成功:${this.age}`);
        } catch (e) {
            console.log("赋值失败:", (e as Error).message);
        }
    }
}

console.log("----顺序edn----")
console.log("\n")
const p: any = new MyClass("张三", 18);

console.log("打印注入属性=>[__name]:", p.__name)//打印注入属性=>[__name]
p.getTheName();//执行注入方法 [getTheName] => 打印[__name]

p.copyMethod1();//执行注入方法 [copyMethod1] => 拷贝自方法:this.method1

p.method1();//执行默认方法 已被装饰器注入 =>执行前会打印 [准备执行方法] =>method1
p.temp();//执行空方法 已被构造器创建 => 拷贝自方法:this.method1

p.logAge();//执行默认方法 打印当前年龄

p.setAge(20)//设置年龄20 => 大于18:正常赋值 ✓
p.setAge(30)//设置年龄30 => 大于18:正常赋值 ✓
p.setAge(10)//设置年龄10 => 小于18:抛出错误 ×
p.logAge()//打印当前年龄 =>30
p.setAge(19)//设置年龄19 => 大于18:正常赋值 ✓
p.logAge()//打印当前年龄 =>19

/* 控制台打印:

----装饰器顺序----
类工厂:2
类工厂:3
属性工厂:创建minAge装饰器
方法装饰器:注入=>方法执行前打印
属性装饰器:minAge => 拦截年龄小于18的复制的赋值
类装饰器:4
类装饰器:3
类装饰器:2
类装饰器:1
类装饰器:原型注入/修改
----顺序edn----


打印注入属性=>[__name]: other name
[getTheName] =>  other name

[准备执行方法] => method1
[method1] => Hi,我是 张三.
[执行完毕] => method1 


[准备执行方法] => method1
[method1] => Hi,我是 张三.
[执行完毕] => method1 


[准备执行方法] => method1
[method1] => Hi,我是 张三.
[执行完毕] => method1 

[logAge] => 我的年龄是 18.
赋值成功:20
赋值成功:30
赋值失败: age 必须 >= 18, got 10
[logAge] => 我的年龄是 30.
赋值成功:19
[logAge] => 我的年龄是 1

 */

参考文档 - 中文翻译,演示说明用途

🌐 装饰器上下文类型(翻译与说明)

1. 装饰器总体

type DecoratorContext =
    | ClassDecoratorContext        // 类装饰器上下文
    | ClassMethodDecoratorContext  // 方法装饰器上下文
    | ClassGetterDecoratorContext  // getter 装饰器上下文
    | ClassSetterDecoratorContext  // setter 装饰器上下文
    | ClassFieldDecoratorContext   // 字段装饰器上下文
    | ClassAccessorDecoratorContext// accessor 装饰器上下文

👉 意思:装饰器会根据目标不同,传入不同的 上下文对象,你可以通过 context.kind 判断是 类 / 方法 / 字段 / getter / setter


2. 类装饰器 ClassDecoratorContext

  • kind: "class" → 说明是类装饰器

  • name → 类名

  • addInitializer(fn) → 在类定义完成后执行初始化逻辑

  • metadata → 装饰器元数据

📌 用途示例:注册自定义组件

function customElement(name: string) {
  return (target: any, context: ClassDecoratorContext) => {
    context.addInitializer(function () {
      customElements.define(name, this);
    });
  };
}

@customElement("my-element")
class MyElement {}

👉 在类加载完成时,把它注册到浏览器的 customElements


3. 方法装饰器 ClassMethodDecoratorContext

  • kind: "method" → 方法

  • name → 方法名

  • static → 是否静态方法

  • private → 是否私有方法

  • access.get(object) / access.has(object) → 运行时获取方法引用

  • addInitializer(fn) → 在方法初始化前执行

📌 用途示例:自动绑定 this

function bound(value: Function, context: ClassMethodDecoratorContext) {
  if (context.private) throw new Error("不支持私有方法");
  context.addInitializer(function () {
    this[context.name] = this[context.name].bind(this);
  });
}

class C {
  message = "Hello";

  @bound
  greet() {
    console.log(this.message);
  }
}

👉 自动把方法绑定到实例,避免 this 丢失。


4. Getter 装饰器 ClassGetterDecoratorContext

  • kind: "getter"

  • name

  • access.get(object) → 调用 getter

  • addInitializer(fn) → 在 getter 可用前执行逻辑

📌 用途示例:日志拦截

function logGetter(value: any, context: ClassGetterDecoratorContext) {
  return function (...args: any[]) {
    console.log(`调用 getter: ${String(context.name)}`);
    return value.apply(this, args);
  };
}

class User {
  first = "Tom";
  last = "Smith";

  @logGetter
  get fullName() {
    return `${this.first} ${this.last}`;
  }
}

5. Setter 装饰器 ClassSetterDecoratorContext

  • kind: "setter"

  • name

  • access.set(object, value) → 调用 setter

  • addInitializer(fn)

📌 用途示例:自动校验

function minAge(min: number) {
  return function (value: any, context: ClassSetterDecoratorContext) {
    return function (this: any, newValue: number) {
      if (newValue < min) throw new Error(`年龄必须大于 ${min}`);
      return value.call(this, newValue);
    }
  }
}

class Person {
  #age = 0;

  get age() { return this.#age }
  @minAge(18)
  set age(v: number) { this.#age = v }
}

6. 字段装饰器 ClassFieldDecoratorContext

  • kind: "field"

  • name → 字段名

  • access.get(object) / access.set(object, value) → 获取 / 设置字段值

  • addInitializer(fn) → 字段初始化后执行

📌 用途示例:给字段设置默认值

function defaultValue<T>(val: T) {
  return function (target: any, context: ClassFieldDecoratorContext) {
    context.addInitializer(function () {
      if (this[context.name] === undefined) {
        this[context.name] = val;
      }
    });
  };
}

class User {
  @defaultValue("Guest")
  name!: string;
}

console.log(new User().name); // Guest

7. Accessor 装饰器 ClassAccessorDecoratorContext

  • kind: "accessor"

  • access.get(object) / access.set(object, value) → 操作 accessor

  • 可以返回一个 ClassAccessorDecoratorResult 替换原 getter/setter/init

📌 用途示例:数据转换

function uppercase(target: any, context: ClassAccessorDecoratorContext) {
  return {
    set(this: any, value: string) {
      return target.set.call(this, value.toUpperCase());
    }
  }
}

class User {
  @uppercase accessor name = "";
}

const u = new User();
u.name = "tom";
console.log(u.name); // TOM

✅ 总结

  • 类装饰器 → 操作整个类

  • 方法装饰器 → 修改方法 / 自动绑定 this

  • getter/setter 装饰器 → 控制访问器逻辑

  • 字段装饰器 → 修改字段默认值 / 校验

  • accessor 装饰器 → 修改 accessor 的 getter/setter

👉 这些接口就是 TypeScript 给装饰器提供的 上下文 API,帮助我们在编译期/运行时修改类和成员的行为。


评论