由浅入深了解ts装饰器

53 阅读3分钟

最近在实现ts版本的ioc容器时,遇到一些ts装饰器疑惑,而网上现在对这块ts相互依赖没有看到好的解题思路,所以分享下自己探索ts装饰器的历程。

ts装饰器的介绍(这个不深入介绍。网上的介绍都有)

分析ts装饰器的源码

import "reflect-metadata"

export function Class(name?: string): ClassDecorator {
    return function (target) {
        console.log("start exec Class decorator on:", target.name, ",name:", name)
    }
}

export function Field(name?: string): PropertyDecorator {
    return function (target: any, propertyKey: string | symbol) {
        let v = Reflect.getMetadata('design:type', target, propertyKey)
        console.log("start exec Field decorator on:", target.constructor.name, ".", propertyKey, ",name:", name,",v:",v)

    }
}

说明

  • 1.上面的代码定义了2种类型装饰器Class,Field.Class装饰在类上,Field装饰在对象字段属性上。
  • 2.里面的逻辑分别打印下log表明来源。
  • 3.Field装饰器获取该字段声明的class类型。

修饰的类

import {Class, Field} from "./decorators";
import {B} from "./B";

@Class()
export class A {
    @Field()
    private b: B
}

class B

export class B{}

执行下 index.ts

require("./A")

执行结果

start exec Field decorator on: A . b ,name: undefined ,v: [class B]
start exec Class decorator on: A ,name: undefined

打印结果符合预期。

相互依赖

如果给B类加上依赖A的装饰器

import {Class, Field} from "./decorators";
import {A} from "./A";

@Class()
export class B{
    @Field()
    private a:A
}

这时候在执行结果不符合预期,Class B.a 拿不到a字段的声明的class。结果如下

start exec Field decorator on: B . a ,name: undefined ,v: undefined
start exec Class decorator on: B ,name: undefined
start exec Field decorator on: A . b ,name: undefined ,v: [class B]
start exec Class decorator on: A ,name: undefined

相互依赖.nodejs18官方介绍

*When there are circular require() calls, a module might not have finished executing when it is returned. When index.js loads A.js, then A.js in turn loads B.js. At that point, B.js tries to load A.js. In order to prevent an infinite loop, an unfinished copy of the A.js exports object is returned to the B.js module. B.js then finishes loading, and its exports object is provided to the A.js module. * * 当存在循环“require()”调用时,模块在返回时可能尚未完成执行。 当“index.js”加载“A.js”时,“A.js”会依次加载“B.js”。此时,“B.js”会尝试加载“A.js”。 为了防止无限循环,“A.js”导出对象的未完成的副本将返回到“B.js”模块。 然后“B.js”完成加载,并将其“exports”对象提供给“A.js”模块。 *

分析下类B.js 的源码

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.B = void 0;
const decorators_1 = require("./decorators");
const A_1 = require("./A");
let B = class B {
};
exports.B = B;
__decorate([
    (0, decorators_1.Field)(),
    __metadata("design:type", A_1.A)
], B.prototype, "a", void 0);
exports.B = B = __decorate([
    (0, decorators_1.Class)()
], B);
  • 1 第14行 当require(A)的时候,返回A_1对象 如下
{ A: undefined }
  • 2 第20行 A_1.A 是 undefined,导致B.a的声明类型无法正常获取

ioc容器:解决思路

  • 1 从业务设计上避免类的相互依赖。对业务层改动较高。
  • 2 从字段名或者装饰器参数来指定这种关联。比如类名的小驼峰开头。