V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
rikka
V2EX  ›  TypeScript

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

  •  
  •   rikka · 2020-07-09 12:03:34 +08:00 · 3034 次点击
    这是一个创建于 1575 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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 方法完全没问题,但现在这样我就很懵,怎么解决

    第 1 条附言  ·  2020-07-09 15:41:46 +08:00
    总结下来解决方法有三

    1. (this as BookmarkModel).save()//我自己找到,感觉就不太好看

    2. _target.save.apply(this, args)
    by @optional

    3. descriptor.value = function (this: YourTypeHere, ...args: never): boolean {
    by @oott123

    个人最喜欢第 3 个解决方法
    感谢各位
    18 条回复    2020-07-10 17:26:48 +08:00
    zhyl
        1
    zhyl  
       2020-07-09 12:12:02 +08:00 via Android
    1. 参数里指定 this 类型。
    2. this 转换成 any 再调用。
    rikka
        2
    rikka  
    OP
       2020-07-09 13:16:02 +08:00
    初学 TS 瞎试一番,(this as MyClass).save(),这样就解决了,不过看起来似乎不太优雅?
    oott123
        3
    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
        4
    optional  
       2020-07-09 13:43:22 +08:00
    这里你用 this 干嘛,用_target 啊,_target 不就是你的 this 吗?
    rabbbit
        5
    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
        6
    vuevue  
       2020-07-09 13:48:06 +08:00 via Android
    函数调用时用 bind(this)试试
    rikka
        7
    rikka  
    OP
       2020-07-09 14:23:55 +08:00
    @oott123 #3 `descriptor.value = function (this: YourTypeHere, ...args: never)`这么搞相当于改变原函数的参数列表,调用方法的时候还得额外传个 this 参数,不行
    rikka
        8
    rikka  
    OP
       2020-07-09 14:25:48 +08:00
    @optional #4 https://es6.ruanyifeng.com/#docs/decorator

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

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

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

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

    看右边的编译结果,this 会被编译掉,只做类型提示使用。
    rikka
        13
    rikka  
    OP
       2020-07-09 15:35:37 +08:00
    @optional #11 你这样确实也可以,不过应该是_target.save.apply(this,args)
    rikka
        14
    rikka  
    OP
       2020-07-09 15:36:21 +08:00
    @oott123 #12 原来可以是这样啊,没试过,学习了
    rabbbit
        15
    rabbbit  
       2020-07-09 17:36:43 +08:00
    不一定非要覆盖 PropertyDescriptor , 你可以用 extends 扩展它.
    如果 一开始不存在 save 方法, 之后可能会添加. 则要在创建时使用 ?: 表示可能存在这个属性.
    rikka
        16
    rikka  
    OP
       2020-07-09 18:33:51 +08:00
    @rabbbit #15 save 方法既不是,也不应该是 PropertyDescriptor 的成员,即便不覆盖扩展它也是非常不合理的做法吧
    Justin13
        17
    Justin13  
       2020-07-09 19:04:09 +08:00 via Android
    miloooz
        18
    miloooz  
       2020-07-10 17:26:48 +08:00
    第三个是官网介绍上的方法,编译会去掉 this 这个参数。官网推荐就挺好。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1286 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 23:23 · PVG 07:23 · LAX 16:23 · JFK 19:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.