Vue.js 中的方法、计算属性和侦听器

Avatar of Sarah Drasner
Sarah Drasner 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

我喜欢使用 Vue 的原因之一是 methodscomputedwatchers 的实用性和它们之间清晰的区别。在理解这三者之前,很难充分利用 Vue 的功能。然而,我发现许多对 Vue 感到困惑的人也对它们之间的区别感到困惑,所以让我们深入了解一下。

如果您需要快速答案,没有时间阅读整篇文章,这里有一个简短的 TL;DR

  • 方法 (Methods):顾名思义(耶,命名!),它们是挂载在对象上的函数——通常是 Vue 实例本身或 Vue 组件。
  • 计算属性 (Computed):这些属性乍一看似乎可以用作方法,但实际上并非如此。在 Vue 中,我们使用 data 来跟踪我们希望使其具有响应性的特定属性的变化。计算属性允许我们定义一个与 data 使用方式相同的属性,但它还可以包含一些基于其依赖项缓存的自定义逻辑。您可以将计算属性视为 查看数据另一种方式
  • 侦听器 (Watchers):这些允许您一窥响应式系统。Vue 提供了一些钩子,我们可以使用它们来观察 Vue 存储的任何属性。如果我们希望在每次发生更改时添加一些功能,或对特定更改做出响应,我们可以监听一个属性并应用一些逻辑。这意味着侦听器的名称必须与我们尝试观察的内容匹配。

如果以上任何内容听起来令人困惑,请不要担心!我们将在下面进一步深入探讨,并希望解决任何困惑。如果您已经熟悉原生 JavaScript,那么除了一个或两个注意事项外,方法可能对您来说非常明显。然后,您可能需要(我非常喜欢这个短语)跳到 计算属性 (Computed)侦听器 (Watchers) 部分。

方法 (Methods)

在使用 Vue 时,您可能会经常使用方法。它们被恰当地命名为,本质上,我们将一个函数挂载到一个对象上。它们对于将功能连接到事件的指令,或者仅仅创建一些可以像其他任何函数一样重复使用的逻辑非常有用。例如,您可以在另一个方法中调用一个方法。您也可以在生命周期钩子中调用一个方法。它们非常通用。

这是一个简单的演示

查看 CodePen 上 Sarah Drasner (@sdras) 编写的 方法的简化示例

<code class="language-css"><div id="app">
  <button @click="tryme">Try Me</button>
  <p>{{ message }}</p>
</div>
new Vue({
  el: '#app',
  data() {
    return {
      message: null
    }
  },
  methods: {
    tryme() {
      this.message = Date()
    }
  }
})

我们也可以在指令本身中执行逻辑,例如 <button @click="message = Date()">Try Me</button>,这对于这个简单的示例非常有效。但是,随着应用程序复杂性的增加,我们更常见地像上面那样将其分解出来以保持可读性。Vue 也限制了您可以在指令中表达的逻辑——例如,允许使用表达式,但不允许使用语句。

您可能会注意到,我们能够在该组件或 Vue 实例中访问此方法,并且我们可以在此处调用我们的任何数据,在本例中为 this.message。您不必像在指令中调用函数那样调用方法。例如,@click=”methodName()” 是不必要的。您可以使用 @click=”methodName” 来引用它,除非您需要传递参数,例如 @click=”methodName(param)”

使用指令调用方法也很好,因为我们有一些现有的修饰符。一个非常有用的示例是 .prevent,它将阻止提交事件重新加载页面,使用方法如下:

<form v-on:submit.prevent="onSubmit"></form>

还有更多,这里只是一些

计算属性 (Computed)

计算属性对于操作已存在的数据非常有价值。任何时候,当您构建需要筛选大量数据并且不想在每次按键时都重新运行这些计算的内容时,请考虑使用计算属性。

一些好的候选项包括(但不限于):

  • 用户在输入时更新大量信息,例如过滤列表
  • 从您的 Vuex 存储中收集信息
  • 表单验证
  • 根据用户需要查看的内容而变化的数据可视化

计算属性是理解 Vue 的重要组成部分。它们是基于其依赖项缓存的计算结果,并且仅在需要时更新。如果使用得当,它们具有极高的性能,并且非常实用。许多大型库处理此类逻辑,而您现在只需几行代码即可消除它们。

计算属性的使用方式与方法不同,尽管起初它们看起来可能很相似——您在函数中声明一些逻辑并返回——但该函数的名称会成为一个属性,然后您可以在应用程序中像使用 data 一样使用它。

如果我们需要根据用户输入的内容来过滤这个英雄名称的大列表,我们可以这样做。我们将其保持简单,以便您可以掌握基本概念。最初,我们的列表将使用存储在 data 中的 names 在模板中输出。

new Vue({
  el: '#app',
  data() {
    return {
      names: [
        'Evan You',
        'John Lindquist',
        'Jen Looper',
        'Miriam Suzanne',
        ...
      ]
    }
  }
})
<div id="app">
  <h1>Heroes</h1>
  <ul>
    <li v-for="name in names">
      {{ name }}
    </li>
  </ul>
</div>

现在,让我们为这些名称创建一个过滤器。我们将首先创建一个带有 v-model 的输入框,该输入框最初将是一个空字符串,但我们最终将用它来匹配和过滤我们的列表。我们将此属性称为 findName,您可以在输入框和 data 中看到对它的引用。

<label for="filtername">Find your hero:</label>
<input v-model="findName" id="filtername" type="text" />
data() {
  return {
    findName: '',
    names: [
      'Evan You',
      'John Lindquist',
      ...
    ]
  }
}

现在,我们可以创建计算属性,它将根据用户在输入框中键入的内容(即 findName 属性中的任何内容)过滤所有名称。您会注意到,我在这里使用了正则表达式以确保不区分大小写,因为用户通常在输入时不会使用大写字母。

computed: {
  filteredNames() {
    let filter = new RegExp(this.findName, 'i')
    return this.names.filter(el => el.match(filter))
  }
}

现在,我们将更新模板中使用的输出内容:

<ul>
  <li v-for="name in names">
    {{ name }}
  </li>
</ul>

…改为:

<ul>
  <li v-for="name in filteredNames">
    {{ name }}
  </li>
</ul>

它会为我们进行每次按键的过滤!我们只需添加几行代码即可使其工作,并且无需加载任何其他库。

查看 CodePen 上 Sarah Drasner (@sdras) 编写的 使用计算属性过滤列表-结束

我无法告诉你使用它们节省了多少时间。如果您正在使用 Vue 并且尚未探索它们,请务必尝试,您会感谢自己的。

侦听器 (Watchers)

Vue 具有良好的抽象,任何有过编程经验的程序员通常都会告诉你,抽象可能很痛苦,因为你最终会遇到它们无法解决的用例。但是,这种情况已经考虑在内,因为 Vue 允许我们更深入地访问响应式系统,我们可以将其用作钩子来观察任何正在发生变化的内容。这非常有用,因为作为应用程序开发者,我们负责的大部分内容都是变化的

侦听器还允许我们编写更具声明性的代码。您不再需要自己跟踪所有内容。Vue 已经在幕后完成了这项工作,因此您还可以访问对它跟踪的任何属性(例如 datacomputedprops)所做的更改。

侦听器非常适合在属性发生变化时执行应用于其他内容的逻辑(我第一次听到这种说法是从 Chris Fritz 那里听到的,但他表示也可能从其他人那里听到过☺️)。这不是一个硬性规则——您绝对可以使用侦听器来处理引用属性本身的逻辑,但它是一种很好的方式来了解侦听器与计算属性的不同之处,其中更改将参考我们打算使用的属性。

让我们浏览一下最简单的示例,以便您了解这里发生了什么。

new Vue({
  el: '#app', 
  data() {
    return {
      counter: 0
    }
  },
  watch: {
    counter() {
      console.log('The counter has changed!')
    }
  }
})

如您在上面的代码中看到的,我们将 counter 存储在 data 中,并且通过使用属性名称作为函数名称,我们能够监听它。当我们在 watch 中引用 counter 时,我们可以观察对该属性的任何更改。

使用侦听器转换状态

如果状态足够相似,你甚至可以简单地使用 watch 来切换状态。这是一个我从头开始用 Vue 创建的图表示例。随着数据变化,watch 会更新它,并在它们之间进行简单的过渡。

SVG 也非常适合这种任务,因为它是用数学构建的。

查看 CodePen 上 Sarah Drasner (@sdras) 的作品:用 Vue 制作的图表,状态过渡

watch: {
  selected: function(newValue, oldValue) {

    var tweenedData = {}

    var update = function () {
      let obj = Object.values(tweenedData);
      obj.pop();
      this.targetVal = obj;
    }

    var tweenSourceData = { onUpdate: update, onUpdateScope: this }

    for (let i = 0; i < oldValue.length; i++) {
      let key = i.toString()
      tweenedData[key] = oldValue[i]
      tweenSourceData[key] = newValue[i]
    }

    TweenMax.to(tweenedData, 1, tweenSourceData)
  }
}

这里发生了什么?

  • 首先,我们创建了一个虚拟对象,它将由我们的动画库更新。
  • 然后我们有一个 update 函数,它在每个补间步骤上都被调用。我们用它来推送数据。
  • 然后我们创建一个对象来保存要进行补间的源数据和更新事件的函数指针。
  • 我们创建一个 for 循环,并将当前索引转换为字符串。
  • 然后我们可以对我们的目标虚拟对象进行补间,但我们只会针对特定的键进行操作。

我们也可以在 watch 中使用动画来创建类似于这种时间差拨盘的东西。我经常旅行,我所有的同事都在不同的地区,所以我想找到一种方法来跟踪我们所有人的当地时间,以及白天/黑夜变化的一些指示。

查看 CodePen 上 Sarah Drasner (@sdras) 的作品:Vue 时间对比

这里我们正在观察 checked 属性,并且我们将触发不同的方法,这些方法包含时间线动画,根据与当前时间的相对关联来改变色调、饱和度和其他一些元素。如前所述,更改发生在下拉菜单上,*但我们正在执行的是应用于其他地方的逻辑*。

watch: {
  checked() {
    let period = this.timeVal.slice(-2),
      hr = this.timeVal.slice(0, this.timeVal.indexOf(':'));

    const dayhr = 12,
      rpos = 115,
      rneg = -118;

    if ((period === 'AM' && hr != 12) || (period === 'PM' && hr == 12)) {
      this.spin(`${rneg - (rneg / dayhr) * hr}`)
      this.animTime(1 - hr / dayhr, period)
    } else {
      this.spin(`${(rpos / dayhr) * hr}`)
      this.animTime(hr / dayhr, period)
    }

  }
},

watch 还有一些其他的有趣之处,例如:我们可以访问属性的新旧版本作为参数,如果我们想观察嵌套对象,我们可以指定 deep。有关更详细的信息,指南中有很多好的信息

您可以看到 watch 在任何需要更新的事情中都非常有用——无论是表单输入、异步更新还是动画。如果您好奇 Vue 中的响应性是如何工作的,指南的这部分 非常有帮助。如果您想了解更多关于响应性的信息,我非常喜欢 Andre Staltz 的文章 和 Mike Bostock 的 A Better Way to Code 中的响应性部分。

总结

我希望这篇文章对如何使用每一个方法进行了有帮助的分解,并通过有效地使用 Vue 加速你的应用程序开发过程。有一个数据表明,作为程序员,我们 70% 的时间花在阅读代码上,30% 的时间花在编写代码上。就我个人而言,我非常喜欢这一点,作为一个维护者,我可以查看我从未见过的代码库,并立即知道作者通过 methodscomputedwatchers 之间的区别所表达的意图。