VueJs,计算属性和观察者之间的区别?

时间:2017-04-07 12:06:22

标签: vue.js vuejs2

在Vue.js文档中有一个如下例子:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

以上代码是必要且重复的。将其与计算属性版本进行比较:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

观察者比计算属性更合适的情况是什么?我该如何决定选择哪个?文档一直说它更“通用”,但并没有真正实现其目的。

6 个答案:

答案 0 :(得分:35)

计算属性

计算属性样本:

computed: {
   val () {
     return this.someDataProperty * someOtherVariable
   }
}

这段特殊代码的作用是什么?

  1. 它为组件创建了一个名为val的属性(在原型上,因此<vueInstanece>.hasOwnProperty('val')将显示false)。

  2. 它有一个依赖树,在这种情况下由被动属性(数据属性,其他计算属性)组成:this.someDataProperty,这意味着依赖关系发生变化的时刻,计算属性将重新计算。

  3. 虽然辩论过,但不能传递参数。像

    这样的东西
    computed: {
      val (flag) {
        return (flag === 1) 
          ? this.someDataProperty * someOtherVariable 
          : this.someDataProperty * 5
        }
    }
    

    无法完成

  4. [编辑] 请参阅:https://vuejs.org/v2/guide/computed.html#Computed-Setter

    观察

    观察员样本:

    watch: {
       val (n, o) {
         console.log(n, o)
       }
    }
    
    1. 它不会创建任何新属性,但会监视对被动属性的更改。

    2. 仅监视一个特定属性,与计算任何依赖属性更改可能导致重新计算的属性不同。

    3. 有新旧价值的论据。

    4. 如果符合以下条件,那么计算属性将成为:

      您想要一个始终依赖于其他属性的属性。就像模板的文本格式一样,这甚至是代码中的示例。

      或者减少变量长度,因为这很常见:

      this.$store.state.someProperty.someNestedProperty.someDeeplyNestedProperty
      

      可以简化为:

      computed: {
        someDeeplyNestedProperty () {
           return this.$store.state.someProperty.someNestedProperty.someDeeplyNestedProperty
        }
      }
      

      不仅减少了可变大小,每次商店更新时,您都会在someDeeplyNestedProperty中获得最新值。

      观察者非常有用,如果您想查看一个反应属性是否已更改为有利值,以了解您已准备好执行操作。

      像:

      watch: {
        somethingSelected() {
          this.router.push('someOtherRoute')
        }
      }
      

答案 1 :(得分:19)

计算属性有一个非常特殊的用途:编写从其他数据派生的新数据。只要您拥有某些数据并在模板中使用它之前需要对其进行转换,过滤或以其他方式对其进行操作,就会使用它们。

计算属性总是必须返回一个值,不应该有任何副作用,并且它们必须是同步的。

因此,在某些情况下,计算属性对您没有帮助,例如:您的组件接收道具,并且每当道具发生更改时,您的组件都必须发出ajax请求。为此,你需要一个观察者。

观察者与计算属性一样频繁,因此您应该始终考虑计算属性是否可以解决您的问题,并且如果不是这种情况,则只考虑观察者(或有时是方法)。

答案 2 :(得分:8)

Vue.js是被动的

这意味着它能够对用户输入和数据更改等内容做出反应。我建议阅读反应系统,以便更好地了解Vue在观察到数据变化时使用的机制。有三种主要方法可以让您的组件利用Vue的反应特性。这些是方法,计算属性和观察者。如果没有仔细审查,这些选项可能看似可以互换(在某些方面它们是可以的),但每个选项都有其最佳用例场景。为了帮助说明这些示例,我将制作一个小型评分应用程序,允许教师为班上的学生输入测试成绩,查看平均成绩并设置脚手架以进行自动保存功能。

<强>方法

TL; DR - 当您想要更改组件的状态或发生的事件与要变异的实例数据不一定相关时使用方法。方法可以接受参数但不跟踪任何依赖性。当您使用方法时,它通常会在组件中创建一些副作用,并且每次重新加载组件时都会运行方法。这意味着如果UI经常更新,则此方法(以及组件上的任何其他方法)也将运行。这可能会导致性能问题或在UI中滞后。

以下是我们评分应用的开始。我知道,没有验证或任何东西,它不漂亮。我们的数据对象(学生姓名和分数)中有一小组测试。我们可以使用一种方法将另一个测试对象添加到我们的数据属性'tests'。

new Vue({
  el: "#app",
  data: {
    newTest: {
      studentName: '',
      score: 0
    },
    tests: [{
      studentName: "Billy",
      score: 76
    }, {
      studentName: "Suzy",
      score: 85
    }, {
      studentName: "Johnny",
      score: 89
    }, {
      studentName: "Emma",
      score: 93
    }]
  },
  methods: {
    addTestScore: function() {
      this.tests.push({
        studentName: this.newTest.studentName,
        score: this.newTest.score
      });
      this.newTest.studentName = '';
      this.newTest.score = 0;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

<body>
  <div id="app">
    <ul>
      <li v-for="test in tests">
        {{test.studentName}} - {{test.score}}
      </li>
    </ul>
    <span>Student</span>
    <input v-model="newTest.studentName">
    <span>Score</span>
    <input v-model="newTest.score">
    <button @click="addTestScore">Add </button>
  </div>
</body>

计算属性

TL; DR - 当您想要改变依赖于另一个被更改属性的属性时,请使用计算属性。计算属性通常依赖于其他数据属性。对依赖属性的任何更改都将触发计算属性的逻辑。计算属性根据其依赖关系进行缓存,因此只有在依赖关系发生更改时才会重新运行。 (例如,返回新Date()的计算属性永远不会重新运行,因为逻辑永远不会运行超过1次。默认情况下,计算属性是getter,但如果需要可以设置setter函数来实现类似的功能。

在我们的评分应用中,我们希望在输入更多数据时跟踪平均测试分数。让我们添加一个名为“average”的计算属性,它将返回数据集中测试的平均分数。每当我们添加另一个测试分数时,“平均”计算属性都会更新。

new Vue({
  el: "#app",
  data: {
    newTest: {
      studentName: '',
      score: 0
    },
    tests: [{
      studentName: "Billy",
      score: 76
    }, {
      studentName: "Suzy",
      score: 85
    }, {
      studentName: "Johnny",
      score: 89
    }, {
      studentName: "Emma",
      score: 93
    }]
  },
  computed: {
    average: function() {
      var sum = this.tests.reduce(function(acc, test) {
        return acc + Number(test.score);
      }, 0);
      return (sum / this.tests.length).toFixed(2);
    }
  },
  methods: {
    addTestScore: function() {
      this.tests.push({
        studentName: this.newTest.studentName,
        score: this.newTest.score
      });
      this.newTest.studentName = '';
      this.newTest.score = 0;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>

<body>
  <div id="app">
    <span>Average Score: {{average}}</span>
    <ul>
      <li v-for="test in tests">
        {{test.studentName}} - {{test.score}}
      </li>
    </ul>
    <span>Student</span>
    <input v-model="newTest.studentName">
    <span>Score</span>
    <input v-model="newTest.score">
    <button @click="addTestScore">Add</button>
  </div>
</body>

<强> WATCHERS

TL; DR - 当您需要根据特定数据属性发生的更改执行某些逻辑时使用观察者。被监视的属性仅对一个属性起作用。当您希望执行异步或昂贵的操作以响应更改的数据时,这非常有用。请记住,观察者仅在特定数据属性发生更改时才会更改。

让我们假装我们的小评分应用程序的最终用户是一位有300个测试成绩的教授。这可能需要很长时间。如果我们的最终用户到达测试堆的末尾并忘记手动点击保存,则自动保存功能会很好。在我们的代码中,我们将一个观察者添加到我们之前创建的计算属性“average”中。每当它被更改时(由于添加了新的测试分数并且平均值被更新),让我们调用一个新的“自动保存”方法,该方法可用于调用API并保存我们的测试分数。

new Vue({
  el: "#app",
  data: {
    newTest: {
      studentName: '',
      score: 0
    },
    tests: [{
      studentName: "Billy",
      score: 76
    }, {
      studentName: "Suzy",
      score: 85
    }, {
      studentName: "Johnny",
      score: 89
    }, {
      studentName: "Emma",
      score: 93
    }]
  },
  watch: {
    average: function() {
      this.autosave();
    }
  },
  computed: {
    average: function() {
      var sum = this.tests.reduce(function(acc, test) {
        return acc + Number(test.score);
      }, 0);
      return (sum / this.tests.length).toFixed(2);
    }
  },
  methods: {
    addTestScore: function() {
      this.tests.push({
        studentName: this.newTest.studentName,
        score: this.newTest.score
      });
      this.newTest.studentName = '';
      this.newTest.score = 0;
    },
    autosave: function() {
    //pretend we are calling our backend to save the data
      console.log('calling api, saving data');
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

<body>
  <div id="app">
    <span>Average Score: {{average}}</span>
    <ul>
      <li v-for="test in tests">
        {{test.studentName}} - {{test.score}}
      </li>
    </ul>
    <span>Student</span>
    <input v-model="newTest.studentName">
    <span>Score</span>
    <input v-model="newTest.score">
    <button @click="addTestScore">Add</button>
  </div>
</body>

答案 3 :(得分:2)

出于本示例的目的,计算属性确实更好。在利用观察者的例子中,注意到这行代码:

this.fullName = this.firstName + ' ' + val

非常相似:

this.fullName = val + ' ' + this.lastName

两者都有相同的目的,他们正在观察名字或姓氏的变化并相应地更新fullName。但是因为这永远不会改变,fullName总是由firstNamelastName组成,所以我们可以避免大惊小怪并创建一个计算属性。然后,每次firstNamelastName更改时,fullName都会自动更新

在某些情况下,使用观察者会更好。当你想要编写一些异步代码的严重计算时,观察者可能更适合。

例如,如果您有以下内容:

let app = new Vue({
    el: '#app',
    data: {
        name: ""
    }
});

您希望每次name更改时,使用它进行API调用,获取结果并对其进行处理,然后观察者更合适:

watchers: {
    "name": function(newValue, oldValue){
         if(newValue != oldValue)} {
            fetch(url, {method: 'post', body: JSON.stringify({name: this.name})}).then(...);
        }
    }
}

要使用计算属性执行此操作,您必须实现computed get()computed set()属性,这将导致更多代码。

另请注意,在文档的示例中,我们有一个属性fullName组成 a.k.a 计算由另外两个属性组成。在我的例子中,name没有计算,在术语的字面意义上。我们只是想观察它,因此使用计算属性将更多的是黑客而不是设计模式。

答案 4 :(得分:2)

如果要根据某些其他值更改来改变值或执行操作,则可以使用观察程序。一个很好的例子就是当您根据道具设置一个值并且您想对任何更改做出反应时:

Vue.component('my-comp',{
  template: '#my-comp',
  props: ['username'],
  created() {
    this.user = this.username;
  },
  watch:{
    username(val){
      this.user = val;
    }
  },
  data(){
    return{
      user: ''
    }
  }
});

请参阅此JSFiddle:https://jsfiddle.net/fjdjq7a8/

这个例子有点做作,但在现实世界中并没有真正起作用,因为我们没有同步值,所以这是一个真实的例子,我在open source projects之一中使用它:

计算机用于任意操纵数据本身,所以比如连接字符串和计算值。

答案 5 :(得分:1)

观看

当您想要执行异步或昂贵的操作以响应更改的数据时,请使用watch。

计算

在其他情况下使用计算。计算的属性根据其依赖性进行缓存。当你只想重新评估它的某些依赖项已经改变时,主要使用它。