NodeJS 如何在Typescript中使方法装饰器示例化对象

ycl3bljg  于 2023-04-05  发布在  Node.js
关注(0)|答案(2)|浏览(136)

我一直在开发一个lambda项目,我们使用lambda-api包。然后我定义了一些名为GetPost的装饰器来将路由Map到lambda API对象。使用这些装饰器,我定义了一个名为ProductApi的类来保存可以使用这些装饰器配置并传递路由路径的方法。它工作得很好。
问题是,当我有一个像ProductApi这样的类时,构造函数永远不会被调用,如果我想添加一些依赖项(比如Service或Repository),它永远不会被定义。在这个例子中,/health路由工作得很好,因为它不使用对象示例中的任何东西,但其他路由不使用。
如何确保构造函数将被调用并定义服务示例?

const api = createAPI();

function Get(path: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        api.get(path, descriptor.value.bind(target));
    };
}

function Post(path: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        api.post(path, descriptor.value.bind(target));
    };
}

class ProductApi {
    private someValue: string;

    constructor(private readonly productService: IProductService = new ProductService()) {
        // this scope does not execute
        this.someValue = "some value";
    }

    @Get('/health')
    async healthCheckr(req: Request, res: Response) {
        console.log(`Executing -- GET /health`);
        // this.someValue does not exists here
        return res.status(200).json({ ok: true });
    }

    @Get('/products')
    async getProducts(req: Request, res: Response) {
        console.log(`Executing -- GET /products`);
        const data = this.productService.getProductsFromService(); // error: Cannot read properties of undefined (reading 'getProductsFromService')
        return res.status(200).json(data);
    }

    @Post('/products')
    async postProducts(req: Request, res: Response) {
        console.log(`Executing -- POST /products`);
        const product = this.productService.saveProduct('Drums', 1200); // erro: Cannot read properties of undefined (reading 'saveProduct')
        return res.status(201).json(product);
    }
}

export const lambdaHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
    console.log('SCOPE lambda');
    return await api.run(event, context);
};

注意:我不想使用框架,我只想在lamda API示例上配置路由的简单方法。

ccrfmcuu

ccrfmcuu1#

与C#不同,在JS中,“方法”只是一个固定在对象上的函数。你可以很容易地将它放在一个变量中,或者将它固定在另一个对象上。这基本上定义了“方法”中的this。而“类”构造函数只是一个创建新对象的函数,并告诉它,“如果有人在寻找你没有的属性,然后,它将该对象作为this执行构造函数中的代码。
简单地说,这就是JS的原型继承,即使JS在此期间接收了class关键字,这仍然是幕后发生的事情。
我为什么要解释这个?
这一行api.get(path, descriptor.value.bind(target));从原型中获取方法,将原型对象永久绑定为this(因此结果函数将只知道原型对象,永远不会看到任何真实的示例) 并使用绑定函数作为该路由的回调。
所以目前,即使那个类会神奇地被示例化(由谁;我不知道)你传递给路由的函数将不知道这一点。
imo.你的装饰器应该看起来更像这样:

function Get(path: string) {
    return function (target: any, methodName: string) {
        if(typeof target[methodName] !== "function"){
          throw new Error("you need to use this decorator with a method.");
        }

        const Class = target.constructor;

        api.get(path, (req: Request, res: Response) => {
          const instance = diContainer.getInstance(Class); // or new Class();
          return instance[methodName](req, res);
        });
    };
}

侧记:迪玛瓦提出了这个主题;这些都是遗留的装饰器。TS在JS中有装饰器规范之前很久就适应了它们。现在有了一个,它与这些遗留的装饰器有很大的不同,TS最终在V5中实现了规范。你(和我)应该更新新的语法并采用它,因为这种语法可能很快就会被弃用。

a7qyws3x

a7qyws3x2#

您需要首先存储有关如何绑定路由的元数据,然后在创建类时应用它
https://tsplay.dev/mbvA4m(可运行)

import type { Request, Response } from 'lambda-api'

type LambdifiedProto = {
  '_lambdifiedGetters'?: Record< /* path */ string, /* propertyKey */ string>
}

function Get(path: string) {
  return function <K extends string, T extends (req: Request, res: Response) => Promise<any>>(
    proto: Record<K, T>, propertyKey: K, descriptor: TypedPropertyDescriptor<T>
  ): void {
    let lproto = proto as LambdifiedProto;
    if (!Object.hasOwn(lproto, '_lambdifiedGetters')) {
      // create or clone from protoproto
      lproto._lambdifiedGetters = { ...lproto._lambdifiedGetters }
    }
    lproto._lambdifiedGetters![path] = propertyKey
    console.log(`registered getter ${propertyKey} on path ${path}`)
  }
}

function Lambda<C extends new (...a: any[]) => any>(klass: C) {
  return class Lambdified extends klass {
    constructor(...a: any[]) {
      super(...a);
      let getters = (klass.prototype as Lambdified)._lambdifiedGetters
      for (let [path, propertyKey] of Object.entries(getters)) {
        console.log('register api: ', { path, propertyKey, this: this })
        // api.register(path, (q, s) => this[propertyKey](q, s))
      }
    }
  }
}

@Lambda
class ProductApi {
  me = 'ProductApi'
  @Get('./health')
  @Get('./v1/health')
  async healthCheckr(req: Request, res: Response) {
    console.log(`Executing -- GET /health`);
    // this.someValue does not exists here
    return res.status(200).json({ ok: true });
  }
}

console.log('...create start...')

new ProductApi()

相关问题