typescript 如何访问目标类型(甚至属性类型?)作为装饰器中的类型参数?

9ceoxa92  于 6个月前  发布在  TypeScript
关注(0)|答案(1)|浏览(68)

我尝试在angular中实现一个deep-watch属性装饰器,用法如下:

@Component({ /* ... */ })
export class AppComp {

  @Watch(
    'a.b.c',
    function (last, current, firstChange) {
      // callback
    }
  )
  prop = { a: { b: { c: 'Hello' } } };
}

字符串
我几乎完成了,除了回调是无类型的,它的thisany,它的lastcurrent参数也是any
使用泛型,我可以向回调函数提供这些类型:

type PropType = { a: { b: { c: string } } };

@Component({ /* ... */ })
export class AppComp {

  //    ↓ generic here
  @Watch<AppComp, PropType>(
    'a.b.c',
    function (last, current, firstChange) {
      // callback
      // this -> AppComp
      // last -> PropType
      // current -> PropType
    }
  )
  prop: PropType = { a: { b: { c: 'Hello' } } };
}


但我认为这种模式是冗长的,因为在这种情况下很明显,回调函数中this的类型应该是AppComplastcurrent参数的类型应该是PropType
下面是Watch定义的简化版本:

interface IWatchDirectiveTarget {
  ngDoCheck?: Function;
}

interface CallbackFunction<ThisType, WatchedType> {
  (this: ThisType, last: WatchedType, current: WatchedType, firstChange: boolean): any;
}

export function Watch<ThisType, WatchedType> (
  path: string,
  cb: CallbackFunction<ThisType, WatchedType>
): (
  target: IWatchDirectiveTarget,
  key: string
) => void {
  return function (
    target: IWatchDirectiveTarget,
    key: string
  ) {
    target.ngDoCheck = function () {
      // some change detecting logic...
      cb.call(this, lastValue, currentValue, isFirst);
    };
  }
}


如您所见,我需要将类型传递到Watch,并再次将这些类型从Watch传递到CallbackFunction
我想要的是像macro这样的东西(举个例子,任何可以达到目的的东西都可以,macro只是我能想到的最简单的东西),比如__CURRENT_CLASS_TYPE____CURRENT_PROPERTY_TYPE__,我可以这样使用它们:

@Watch<__CURRENT_CLASS_TYPE__, __CURRENT_PROPERTY_TYPE__>(path, cb)


虽然看起来很丑,但至少@Watch<__CURRENT_CLASS_TYPE__, __CURRENT_PROPERTY_TYPE__>可以被视为一个整体,并与类名和属性名解耦。
或者更好的是,在Watch的定义中添加一些类似宏的东西,这样就可以在没有外部泛型类型参数的情况下重写它:

function Watch (
  path: string,
  cb: CallbackFunction<__CURRENT_CLASS_TYPE__, __CURRENT_PROPERTY_TYPE__>
): (
  target: IWatchDirectiveTarget,
  key: string
) => void {
  return function (
    target: IWatchDirectiveTarget,
    key: string
  ) {
    target.ngDoCheck = function () {
      // some change detecting logic...
      cb.call(this, lastValue, currentValue, isFirst);
    };
  }
}


这样的事情有可能做到吗?
编辑:
我读过这个:TypeScript property decorator that takes a parameter of the decorated property type
在提供给装饰器工厂的值的类型和装饰属性的类型之间建立某种关系似乎是可能的...

tzdcorbm

tzdcorbm1#

我试着用**“一半”来回答我的问题。沿着post TypeScript property decorator that takes a parameter of the decorated property type的路径,我设法通过将其定义从decorator factory更改为decorator factory**来从用法中删除目标类型:
定义:

interface Fn<TClass, TValue> {
  (this: TClass, last: TValue, current: TValue, firstChange: boolean): any;
}

export function DecoratorFactoryFactory<TValue> ():
  <TClass>(pathOrCb: string | Fn<TClass, TValue>, _cb?: Fn<TClass, TValue>) =>
  (target: TClass, key: string) =>
  void
{
  return function (pathOrCb, _cb) {
    return function (target, key) {
      // "hijack" ngDoCheck, change detecting logic...
    }
  }
}

字符串
使用方法:

@Component({ /* ... */ })
export class AppComponent {

  // two calls here, first call returns the decorator factory, second call returns the decorator
  @DecoratorFactoryFactory<number[] | undefined>()(
    'nestedProp',
    function(last, current, firstChange) {
      console.log('dbg change detected', this, last, current, firstChange);
    }
  )
  public prop;
}


我说“一半”是因为我仍然需要指定值类型,但我认为这是可以接受的,因为我使用字符串字面量来指定值的路径,我不认为TS有能力从字符串字面量推断类型。
1.自动属性类型推断+值?.a?.B?.c
1.手动类型+值
很难说哪个更好。
这个解决方案并不完美,因为在使用中的双重调用对这个装饰器的用户来说是没有意义的,所以我不打算接受我自己的答案。我希望有更好的理解typescript的人可以想出更好的答案(单调用),甚至可以自动推断属性类型的解决方案。

相关问题