如何在 Vue2 中对去抖动的计算 setter 进行类型转换?

问题描述

Vue:2.6.11
打字稿:3.9.3
代码沙盒:here

这是我正在尝试做的简化版本:

<template>
  <input v-model="computedValue" type="number">
</template>
<script lang="ts">
  import Vue from 'vue';
  import { debounce } from 'lodash-es';

  export default Vue.extend({
    name: 'MyInput',props: {
      value: {
        type: Number,required: true
      }
    },computed: {
      computedValue: {
        get(): number {
          return this.value;
        },set: debounce(function(value) {
          this.$emit('update:value',value);
        },500)
      }
    }
  });
</script>

这被父级用作 <my-input :value.sync="someProp" />。它是一个更复杂的表单系统的一部分,具有不同类型的输入,配置驱动。但这与我提出的问题无关。当我不去抖动时,一切都按预期工作。如在,一切都被正确输入和推断。

我的问题是我不知道如何告诉 Typescript 将外部 this 投射到去抖动函数的 this 上,这是 debounce 在幕后所做的事情。

实际的打字稿错误是:

TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.

error screenshot

代码有效(计算的 setter 已去抖动),但 Typescript 抱怨不知道去抖动函数中的 this 是什么。
如果我将 debounced 函数从普通函数更改为箭头函数,Typescript 不再对我吠叫(它现在知道 this 是什么),但代码停止工作。

有谁知道如何在去抖动函数中正确地转换 this 的值?

注意:不建议将 /* @ts-ignore */ 放在上面一行。这就是我目前正在做的事情。

一个注意事项:我的猜测是我必须覆盖 debounce 的定义的类型,它目前来自 @types/lodash-es,但我仍然不知道用什么来替换它。>

解决方法

在匿名函数中使用 this 总是很棘手,因为 this 的值取决于函数的调用方式。 Lodash 可能在这里明确地处理了 this,但 TS 不知道这一点。

最简单的方法通常是在局部变量中“捕获”this 的值,如下所示:

methods: {
  someMethod() {
    const self = this
    setTimeout(function() { self.doSomething() },500)
  }
}

这对于您的代码当然是不可能的但是我认为您的代码还有一个问题,这就是您创建去抖动包装器的方式。它在 Vue 3 文档中是 hinted,但也适用于 Vue 2

这种方法对于重复使用的组件来说可能存在问题,因为它们都将共享相同的去抖动功能。

要解决这个问题(同时解决 TS 问题):

computed: {
      computedValue: {
        get(): number {
          return this.value;
        },set(value): void {
          this.debouncedSetter(value)
        }
      }
    },created() {
  self = this
  this.debouncedSetter = debounce(function(value) {
    self.$emit('update:value',value);
  },500)
}
,

编辑 - 警告:

值得注意的是,正如 Michal Levý 在另一个答案中提到的那样,绝对不推荐直接在计算的 setter 中使用 debounced,原因是他的答案中提到的 -去抖动函数应该每个实例存在,否则一定会出现一些奇怪的行为。话虽如此,这个答案仍然是一种向配置对象中的函数作为参数添加更好的 this 的方法

TLDR:简短的回答是你不能,如果没有太多的篮球是值得的。但是您可以将 this 输入为 Vue,这是次佳的并且对 99% 的情况有用。

问题在于 Typescript 和类型推断:Vue 类型使用 ThisType 将上下文注入到配置对象 (see the source) 中定义的各种方法中,这对于就地定义的函数工作正常,但是使用 debounce 意味着你传递的函数不是这个过程的一部分 - 它只是 debounce 本身的一个参数,所以 TS 不知道向它注入任何东西。

我所知道的最简单的解决方案是将 Vue 注入到函数中。有关详细信息,请参阅 the docs,但要点是:在输入中传递 this 参数作为上下文输入(因为 this 无论如何都是保留字),因此这是有效的打字稿:

    computedValue: {
      get(): number {
        return this.value;
      },set: debounce(function (this: Vue,value: number) {
        this.$emit('update:value',value);
      },500),},

优点:它为您提供了所有 Vue 类型 坏处:除非您手动定义所有内容,否则没有实际上下文:

set: debounce(function (this: CombinedVueInstance<...>,value: number) {

但如果您需要这样做,直接在计算的 setter 中使用 debounce 不再有用(如另一个答案中所述,更容易在 created 中设置去抖动函数)。


OP 编辑​​:我认为分享(对于未来遇到类似问题的用户)Tiago 提出的更优雅的解决方案很重要(随着讨论的继续,在 SO 之外)。它位于 MyInput.vue 内 我们用作游乐场的沙箱组件。