【问题标题】:What's causing the Vue computed property to be computed?是什么导致 Vue 计算属性被计算?
【发布时间】:2021-09-14 07:26:54
【问题描述】:

我对计算属性的理解是,如果模板中没有使用计算属性,则不应该计算它。然而,当有一个计算属性的观察者时,这个计算属性会被计算。

我知道这不是所需的应用程序架构,但我的问题是,它以这种方式工作的事实是否是所需的 Vue 行为,或者是当前实现方式的一些副作用,并且在未来的版本中可能会发生变化?

或者换句话说: 如果计算的属性没有在模板中使用并且在代码库中没有其他显式的 getter(除了观察者),是否应该评估它?

例子:

<template>
  <div>
        <button @click="counter++">+1</button>
        {{ counter }}
  </div>
</template>

<script>
export default {
  name: "Main",
  data() {
    return {
      counter: 0,
    };
  },
  computed: {
    notUsedInTemplate() {
      console.log("notUsedInTemplate computed called");
      return this.counter + 1;
    },
  },
  watch: {
    notUsedInTemplate() {
      console.log("notUsedInTemplate watch called");
    },
  },
};
</script>

沙盒链接:https://codesandbox.io/s/computed-sandbox-ru3n3?file=/src/components/Main.vue:0-818

【问题讨论】:

  • 形成您在计算属性中编写的逻辑,您可以将其放弃并从watch 对象中将计数器加一
  • 组件中的实际逻辑要复杂得多。问题中的一个只是为了更好地描述问题。我知道它应该在watch 中完成,但现实生活中的这种重构不是我现在负担得起的。我想从行业中获得更多的洞察力,以了解这是 Vue 的期望行为还是未来版本中可能会消失的东西。基于这些知识,我可以优先考虑重构。
  • vue 推荐使用计算比较来观看。可能的原因可能是性能,声明式与命令式,计算式易于处理和设置比较以观察。 vuejs.org/v2/guide/computed.html
  • 您能解释一下为什么这对您来说很重要吗?您在计算属性中所做的一切都必须没有副作用。因此,您不得设置/修改其中的任何属性(或调用可能导致此结果的函数)或使用任何非反应性的非本地值(const 可能没问题)。严格来说,您的 console.log("notUsedInTemplate computed called"); 也会产生副作用。因此,从您的值状态和应用程序的角度来看,watch 是否触发对计算属性的调用不会有任何区别。
  • 知道这一点对我来说很重要,因为我更喜欢了解我使用的框架,这种行为让我很惊讶。 @michal-levý 在他的 comment 中提出了一个很好的观点,在浏览了 Vue 的代码库之后,我明白了它为什么会这样工作。

标签: javascript vuejs2 vue-component


【解决方案1】:

如果模板中没有使用计算属性,则不应该计算它

...不正确。正确的说法是:

如果计算的属性没有被使用(在任何地方),它就不应该被评估

使用watch 评估属性。其实watch和模板重渲染很像(在vue 3里重渲染其实没什么特别的watchEffect

因为看(任何东西):

  1. Vue 1st 需要运行代码 - 在本例中为 notUsedInTemplate getter - 以找出它正在访问的响应数据(因此 this.counter 被跟踪为依赖项)
  2. this.counter 发生变化时,观察者再次运行notUsedInTemplate getter,以便将新值与前一个值进行比较
  3. 如果值更改,它会运行回调(在您的示例中记录)

所以回答问题:

  1. 是的,这是期望的行为,因为您的观察者不会跟踪 notUsedInTemplate 计算属性的更改,而是用于计算其值的基础数据
  2. Watcher 正在访问计算属性,无需在其他任何地方使用它

好的,更多细节为什么这样工作

Vue 反应性总是有 2 个阶段:

  1. 第 1 阶段 - 运行代码(无论是模板还是 watch expression/fn)并收集所有依赖项(例如,代码“触及”的任何反应性数据)
  2. 第 2 阶段 - 跟踪依赖项。如果任何依赖发生变化,重新运行回调(或渲染函数)

请参阅文档中的这一章 - How Changes Are Tracked - 该示例明确提到仅模板重新渲染(它使用 Vue 自己创建的观察器),但所有这些都适用于 all watchers,包括用户创建的那些

【讨论】:

  • 观察者最初要求当前值是在代码中定位问题的一种方式。但从技术上讲,也可以在财产发生变化的那一刻进行。我正在搜索文档和测试用例,以找出在什么情况下调用计算属性以及观察者的确切行为方式。但我没有找到明确的答案。请在验证您的声明的文档中添加特定部分的参考/引用。
  • 但从技术上讲,在财产发生变化时也可以这样做 ...我不这么认为。 Vue 怎么知道计算 prop 的值发生了变化而不首先运行它? Vue 反应性总是有 2 个阶段 - 阶段 1 - 运行代码(无论是模板还是监视表达式/fn)并收集所有依赖项。第 2 阶段 - 跟踪依赖项更改并重新运行回调(或渲染函数)
  • How would Vue know that the value of computed prop changed without running it 1st in the first place? 从技术上讲,您可以解析代码以找到相关属性。那里肯定可能有一些有问题的部分,但我不会说这是完全不可能的。但这并不重要,关键是现在可能是这样并不意味着它不能改变。如果在文档中以这种方式指定,则可以保证此行为不会改变。
  • Vue 文档很好,但缺少很多(可以说技术性太强)细节。在这种情况下,code is documentation
  • @MichalLevý 你的评论回答了我的问题。谢谢。
【解决方案2】:

您对计算属性的看法是正确的。计算属性中的任何内容都将附加到 vue 实例并在模板中访问。

所以,如果它没有在模板中使用,它不应该在计算中。

computed 属性是声明性的,而watch 是强制性的。

computed: {
// a computed getter
reversedMessage: function () {
  // `this` points to the vm instance
  return this.message.split('').reverse().join('')
 }
}

最好的部分是我们以声明方式创建了这种依赖关系:计算的 getter 函数没有副作用,这使得测试和理解更容易。

watch 是查找更改并更新值的函数。

watch: {
firstName: function (val) {
  this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
  this.fullName = this.firstName + ' ' + val
 }
}

通过使用计算的 getter 属性,您只是让 vue 可以轻松地以声明的方式跟踪更改和更新,但是您可以使用 watch 做同样的事情,但这是必要的,并且 vue 需要查找更改并且每次更改都需要触发该函数调用并稍后执行该函数。

more info

【讨论】:

  • "所以,如果它没有在模板中使用,它不应该在计算中。" - 但它是。那么它是一个错误吗?还是想要但不完美的行为?
  • 这不是完美的行为,您可以创建计算属性,但您没有将其用于主要用例。
猜你喜欢
  • 2020-02-03
  • 2020-12-01
  • 2021-05-04
  • 2022-11-11
  • 2018-07-29
  • 2021-12-17
  • 2019-10-25
  • 1970-01-01
  • 2017-06-30
相关资源
最近更新 更多