jkchao / typescript-book-chinese

TypeScript Deep Dive 中文版
https://jkchao.github.io/typescript-book-chinese/
MIT License
6.51k stars 675 forks source link

关于类装饰器和元数据的问题 #118

Open zyhcool opened 4 years ago

zyhcool commented 4 years ago

有个疑问请教各位: 在这篇文章此处有段代码:

type Constructor<T = any> = new (...args: any[]) => T;

const Injectable = (): ClassDecorator => target => {};

class OtherService {
  a = 1;
}

@Injectable()
class TestService {
  constructor(public readonly otherService: OtherService) {}

  testMethod() {
    console.log(this.otherService.a);
  }
}

const Factory = <T>(target: Constructor<T>): T => {
  // 获取所有注入的服务
  const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService]
  const args = providers.map((provider: Constructor) => new provider());
  return new target(...args);
};

Factory(TestService).testMethod(); // 1

其中 Injectable 装饰器实质上并没有对 TestService 类进行修改,那为什么这边还要加上这个类装饰器呢? 实验证明,如果不加上的话,Reflect.getMetadata('design:paramtypes', target) 将无效,这是为什么呢?

谢谢解答!

jkchao commented 4 years ago

@zyhcool 可以对比一下加了 @Injectable() 与不加时,TestService 编译后的代码是什么

zyhcool commented 4 years ago

@jkchao 谢谢回复!

我修改了一下代码

// ts源码
import "reflect-metadata";

const Injectable = (): ClassDecorator => target => {};

@Injectable()
class C {
    constructor(s:string){

    }
}

function Factory(constructor){
    console.log(Reflect.getMetadata("design:paramtypes",constructor));
}

Factory(C);

这是加入@Injectable() 后编译的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;
};
exports.__esModule = true;
require("reflect-metadata");
var Injectable = function () { return function (target) { }; };
var C = /** @class */ (function () {
    function C(s) {
    }
    C = __decorate([
        Injectable()
    ], C);
    return C;
}());
function Factory(constructor) {
    console.log(Reflect.getMetadata("design:paramtypes", constructor));
}
Factory(C);

这是去掉@Injectable() 后编译的js代码:

"use strict";
exports.__esModule = true;
require("reflect-metadata");
var Injectable = function () { return function (target) { }; };
var C = /** @class */ (function () {
    function C(s) {
    }
    return C;
}());
function Factory(constructor) {
    console.log(Reflect.getMetadata("design:paramtypes", constructor));
}
Factory(C);

本质上加了@Injectable()后,只是多了一个__decorate函数和其中的Reflect.decorate方法,这个是reflect-metadata写的的polyfill,有什么影响吗? 而且运行编译后的js文件(加了@Injectable()的),打印结果却是undefined,为什么? 难道Reflect.metadata只能在TS中使用,而且一定要在作为装饰器的函数中?

jkchao commented 4 years ago

@zyhcool 使用 Reflect Metadata 需要打开 emitDecoratorMetadata 编译选项,然后再对比一下编译后的结果,其中使用 @ Injectable 的,有一句很关键的代码 __metadata("design:paramtypes", [String]),把 [String] 存储到了 key 为 design:paramtypes 的元信息里面。

zyhcool commented 4 years ago

@jkchao 谢谢大佬,已经解决我的疑惑了! 我之前使用tsc编译确实没有带上--experimentalDecorators--emitDecoratorMetadata选项,加上后得到的编译结果多了个__metadata函数

var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
sultan-young commented 1 year ago

牛!