typescript 用装饰器遇到 this 推导不对问题

2020-07-09 12:03:34 +08:00
 rikka

这是为类的方法写的装饰器函数

function autoSave (_target: BookmarkModel, _name: string, descriptor: PropertyDescriptor): PropertyDescriptor {
  const value = descriptor.value
  descriptor.value = function (...args: never): boolean {
    const ret = value.apply(this, args)
    if (ret === true) {
      this.save()
    }
    return ret
  }
  return descriptor
}

代码强行执行是完全 OK 的,但是 this.save()这行报个错:

类型“PropertyDescriptor”上不存在属性“save”。

this.save()实际就是执行类实例上的 save 方法完全没问题,但现在这样我就很懵,怎么解决

2931 次点击
所在节点    TypeScript
18 条回复
zhyl
2020-07-09 12:12:02 +08:00
1. 参数里指定 this 类型。
2. this 转换成 any 再调用。
rikka
2020-07-09 13:16:02 +08:00
初学 TS 瞎试一番,(this as MyClass).save(),这样就解决了,不过看起来似乎不太优雅?
oott123
2020-07-09 13:39:24 +08:00
如果你清楚自己的 this 是什么类型:

descriptor.value = function (this: YourTypeHere, ...args: never): boolean {

如果 this 和 autoSave 的上下文相同:

descriptor.value = (...args: never): boolean => {
optional
2020-07-09 13:43:22 +08:00
这里你用 this 干嘛,用_target 啊,_target 不就是你的 this 吗?
rabbbit
2020-07-09 13:46:09 +08:00
interface PropertyDescriptor {
  save?: Function;
}

function autoSave(_target: BookmarkModel, _name: string, descriptor: PropertyDescriptor): PropertyDescriptor {
  const value = descriptor.value
  descriptor.value = function (...args: never): boolean {
   const ret = value.apply(this, args)
   if (this.save) { // <--
    this.save();
  }
   return ret
 }
  return descriptor
}
vuevue
2020-07-09 13:48:06 +08:00
函数调用时用 bind(this)试试
rikka
2020-07-09 14:23:55 +08:00
@oott123 #3 `descriptor.value = function (this: YourTypeHere, ...args: never)`这么搞相当于改变原函数的参数列表,调用方法的时候还得额外传个 this 参数,不行
rikka
2020-07-09 14:25:48 +08:00
@optional #4 https://es6.ruanyifeng.com/#docs/decorator

装饰器第一个参数是类的原型对象,上例是 Person.prototype,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时 target 参数指的是类本身);第二个参数是所要装饰的属性名,第三个参数是该属性的描述对象。

实测结果确实如此_target 不是 this 而是类本身
rikka
2020-07-09 14:28:03 +08:00
@rabbbit #5 本来 save 就不是 PropertyDescriptor 里面的方法,强行给他加个定义,并且这样还覆盖掉原版 PropertyDescriptor,不行啊
Jirajine
2020-07-09 14:32:19 +08:00
感觉 typescript 的类型推导特别傻逼,一点都不智能。比如初始化一个空数组就给推导成[never],完全不分析后续使用的上下文。
optional
2020-07-09 14:58:03 +08:00
@rikka 如果你的 save 是直接由 BookmarkModel 定义的,那么它的 prototype 上就有 save 方法啊。

_target.save.call(this,args)不可以吗
oott123
2020-07-09 15:06:01 +08:00
@rikka #7 你确定你试过了吗

https://www.typescriptlang.org/play/index.html#code/GYVwdgxgLglg9mABAQwBRQBYwM4C5HZQBOMYA5gDQpFkCM+YIAtgEYCmRAlIgN4BQfAL5A

看右边的编译结果,this 会被编译掉,只做类型提示使用。
rikka
2020-07-09 15:35:37 +08:00
@optional #11 你这样确实也可以,不过应该是_target.save.apply(this,args)
rikka
2020-07-09 15:36:21 +08:00
@oott123 #12 原来可以是这样啊,没试过,学习了
rabbbit
2020-07-09 17:36:43 +08:00
不一定非要覆盖 PropertyDescriptor , 你可以用 extends 扩展它.
如果 一开始不存在 save 方法, 之后可能会添加. 则要在创建时使用 ?: 表示可能存在这个属性.
rikka
2020-07-09 18:33:51 +08:00
@rabbbit #15 save 方法既不是,也不应该是 PropertyDescriptor 的成员,即便不覆盖扩展它也是非常不合理的做法吧
Justin13
2020-07-09 19:04:09 +08:00
miloooz
2020-07-10 17:26:48 +08:00
第三个是官网介绍上的方法,编译会去掉 this 这个参数。官网推荐就挺好。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/688506

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX