1️⃣ 计算属性

当我们处理复杂逻辑时,都应该使用计算属性。

2️⃣ 基础例子

  1. <template>
  2. <div id="app">
  3. <div>{{ count }}</div>
  4. <div @click="num1++">Add</div>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name: "App",
  10. data: function() {
  11. return {
  12. num1: 10,
  13. num2: 20,
  14. };
  15. },
  16. computed: {
  17. // 计算属性的简写形式
  18. count() {
  19. return this.num1 + this.num2;
  20. },
  21. },
  22. };
  23. </script>

2️⃣ 计算属性缓存 vs 方法

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
当使用方法时,每一次页面重新渲染,对应的方法都会重新执行一次,但是利用计算属性做,就不会有这样的现象出现。计算属性有缓存而方法没有缓存。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

2️⃣ 计算属性 vs 侦听属性

两者都可以观察和响应 Vue 实例上的数据的变动。
侦听器(watch)擅长处理的场景是:一个数据影响多个数据。计算属性(computed)擅长处理的场景是:多个数据影响一个数据。
在侦听器中可以执行异步,但是在计算属性中不可以。

2️⃣ 计算属性的 setter

计算属性除了写成一个函数之外,还可以写成一个对象,对象内有两个属性,getter & setter,这两个属性皆为函数,写法如下:

  1. const vm = new Vue({
  2. el: "#app",
  3. computed: {
  4. setAget: {
  5. getter() {
  6. // 一些代码
  7. },
  8. setter() {
  9. // 一些代码
  10. },
  11. },
  12. },
  13. });

3️⃣ getter 读取

在前面,我们直接将计算属性写成了一个函数,这个函数即为 getter 函数。也就是说,计算属性默认只有 getter。

  1. 1. getter this,被自动绑定为 Vue 实例。

当我们去获取某一个计算属性时,就会执行 get 函数。
get 被调用的时间

  1. 1. 当计算属性被初次调用时
  2. 2. 当计算属性依赖的属性被修改时
  1. <template>
  2. <div id="app">
  3. <!-- 计算属性有缓存机制 多次调用 get 只会被调用一次 -->
  4. <div>{{ count }}</div>
  5. <div>{{ count }}</div>
  6. <div>{{ count }}</div>
  7. <div @click="num1++">Add</div>
  8. </div>
  9. </template>
  10. <script>
  11. export default {
  12. name: "App",
  13. data() {
  14. return {
  15. num1: 10,
  16. num2: 20,
  17. };
  18. },
  19. computed: {
  20. count: {
  21. get() {
  22. console.log("get 被调用了");
  23. let num = this.num1 + this.num2;
  24. return num;
  25. },
  26. },
  27. },
  28. };
  29. </script>

3️⃣ setter 设置

set 函数在给计算属性重新赋值时会执行。set 的参数为被重新设置的值。

  1. 1. setter this,被自动绑定为 Vue 实例。

set 的调用时间

  1. 1. 当计算属性本身被修改时 set 被调用,一般情况下计算属性是不修改的 set 的。

要注意,即使给计算属性赋了值,计算属性也不会改变,只有当依赖的响应式属性变化了,计算属性才会重新计算。

  1. <template>
  2. <div id="app">
  3. <div>{{ count }}</div>
  4. <div @click="count = 50">Add</div>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name: "App",
  10. data() {
  11. return {
  12. num1: 10,
  13. num2: 20,
  14. };
  15. },
  16. computed: {
  17. count: {
  18. get() {
  19. console.log("get 被调用了");
  20. let num = this.num1 + this.num2;
  21. return num;
  22. },
  23. set(newV) {
  24. console.log("set 被调用了");
  25. this.num1 = newV;
  26. },
  27. },
  28. },
  29. };
  30. </script>

1️⃣ 侦听器

侦听属性,响应数据( data&computed )的变化,当数据变化时,会立刻执行对应函数

  1. 1. 侦听器函数,会接收两个参数,第一个参数为 newVal ( 被改变的数据 ),第二个参数为 oldVal ( 赋值新值之前的值 )。
  1. <template>
  2. <div id="app">
  3. <div>{{ num }}</div>
  4. <h3 @click="change">change</h3>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name: "App",
  10. data() {
  11. return {
  12. num: 100,
  13. };
  14. },
  15. watch: {
  16. num(newVal, newOld) {
  17. console.log(newVal, newOld, "触发 watch");
  18. },
  19. },
  20. methods: {
  21. change() {
  22. if (this.num === 100) {
  23. this.num = 200;
  24. } else {
  25. this.num = 100;
  26. }
  27. },
  28. },
  29. };
  30. </script>

2️⃣ 字符串类型

值为方法名字,被侦听的数据改变时,会执行该方法。

  1. <template>
  2. <div id="app">
  3. <div>{{ num }}</div>
  4. <h3 @click="change">change</h3>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name: "App",
  10. data() {
  11. return {
  12. num: 100,
  13. };
  14. },
  15. watch: {
  16. num: "watchMethod",
  17. },
  18. methods: {
  19. change() {
  20. if (this.num === 100) {
  21. this.num = 200;
  22. } else {
  23. this.num = 100;
  24. }
  25. },
  26. watchMethod(newVal, oldVal) {
  27. console.log(newVal, oldVal, "触发 watch 调用指定方法");
  28. },
  29. },
  30. };
  31. </script>

2️⃣ 对象类型

写成对象类型时,可以提供选项。

  1. 1. handlerhandler 是被侦听的数据改变时执行的回调函数。handler 的值类型为函数/字符串,写成字符串时为一个方法的名字。
  2. 2. deep:在默认情况下,侦听器侦听对象只侦听引用的变化,只有在给对象赋值时它才能被监听到。所以需要使用 deep 选项,让其可以发现对象内部值的变化,将 deep 的值设置为 true,那么无论该对象被嵌套的有多深,都会被侦听到。
  3. 3. immediate**:**加上 immediate 选项后,回调将会在侦听开始之后立刻被调用。而不是等待侦听的数据更改后才会调用。
  1. <template>
  2. <div id="app">
  3. <div>{{ num.num1 }}</div>
  4. <h3 @click="change">change</h3>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name: "App",
  10. data() {
  11. return {
  12. num: {
  13. num1: 100,
  14. },
  15. };
  16. },
  17. watch: {
  18. num: {
  19. handler(newVal, newOld) {
  20. console.log(newVal, newOld, "触发 watch");
  21. },
  22. deep: true,
  23. immediate: true,
  24. },
  25. },
  26. methods: {
  27. change() {
  28. if (this.num.num1 === 100) {
  29. this.num.num1 = 200;
  30. } else {
  31. this.num.num1 = 100;
  32. }
  33. },
  34. },
  35. };
  36. </script>

2️⃣ 数组类型

可以将多种不同值类型写在一个数组中。

  1. <template>
  2. <div id="app">
  3. <div>{{ num }}</div>
  4. <h3 @click="change">change</h3>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name: "App",
  10. data() {
  11. return {
  12. num: 100,
  13. };
  14. },
  15. watch: {
  16. num: [
  17. "watchMethod",
  18. function() {
  19. console.log("触发 watch function 方法被调用了");
  20. },
  21. {
  22. handler(newVal, newOld) {
  23. console.log(newVal, newOld, "触发 watch");
  24. },
  25. deep: true,
  26. immediate: true,
  27. },
  28. ],
  29. },
  30. methods: {
  31. change() {
  32. if (this.num === 100) {
  33. this.num = 200;
  34. } else {
  35. this.num = 100;
  36. }
  37. },
  38. watchMethod(newVal, newOld) {
  39. console.log(newVal, newOld, "触发 watch 调用指定方法");
  40. },
  41. },
  42. };
  43. </script>

2️⃣ 键类型

当 key 值类型为字符串时,可以实现监听对象当中的某一个属性,如:

  1. <template>
  2. <div id="app">
  3. <div>{{ num.num1 }}</div>
  4. <h3 @click="change">change</h3>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name: "App",
  10. data() {
  11. return {
  12. num: {
  13. num1: 100,
  14. },
  15. };
  16. },
  17. watch: {
  18. "num.num1"(newVal, newOld) {
  19. console.log(newVal, newOld, "触发 watch");
  20. },
  21. },
  22. methods: {
  23. change() {
  24. if (this.num.num1 === 100) {
  25. this.num.num1 = 200;
  26. } else {
  27. this.num.num1 = 100;
  28. }
  29. },
  30. },
  31. };
  32. </script>

1️⃣ vm.$watch

Vue 实例将会在实例化时调用 $watch,遍历 watch 对象的每一个属性。我们也可以利用 vm.$watch 来实现侦听,用法与 watch 选项部分一致,略有不同。以下为使用方法。

2️⃣ 基本用法

  1. <div>
  2. <p>FullName: {{fullName}}</p>
  3. <p>FirstName: <input type="text" v-model="firstName"></p>
  4. </div>
  5. new Vue({
  6. el: '#root',
  7. data: {
  8. firstName: 'Dawei',
  9. lastName: 'Lou',
  10. fullName: ''
  11. },
  12. watch: {
  13. firstName(newName, oldName) {
  14. this.fullName = newName + ' ' + this.lastName;
  15. }
  16. }
  17. })

2️⃣ handler 方法和 immediate 属性:

上面的例子是值变化时候,watch才执行,我们想让值最初时候watch就执行就用到了**handler****immediate**属性

  1. watch: {
  2. firstName: {
  3. handler(newName, oldName) {
  4. this.fullName = newName + ' ' + this.lastName;
  5. },
  6. // 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法,如果设置了false,那么效果和上边例子一样
  7. immediate: true
  8. }
  9. }

2️⃣ deep 属性( 深度监听,常用于对象下面属性的改变 ):

  1. <div>
  2. <p>obj.a: {{obj.a}}</p>
  3. <p>obj.a: <input type="text" v-model="obj.a"></p>
  4. </div>
  5. new Vue({
  6. el: '#root',
  7. data: {
  8. obj: {
  9. a: 123
  10. }
  11. },
  12. watch: {
  13. obj: {
  14. handler(newName, oldName) {
  15. console.log('obj.a changed');
  16. },
  17. deep: true
  18. }
  19. }
  20. })

2️⃣ 侦听某个数据的变化

  1. // 1. 三个参数,一参为被侦听的数据;二参为数据改变时执行的回调函数;三参可选,为设置的选项对象
  2. vm.$watch(
  3. "msg",
  4. function () {
  5. },
  6. {
  7. deep: true,
  8. immediate: true,
  9. }
  10. );
  11. // 2. 二个参数,一参为被侦听的数据;二参为选项对象,其中 handler 属性为必需,是数据改变时执行的回调函数,其他属性可选。
  12. vm.$watch(
  13. "msg",
  14. {
  15. handler() {
  16. },
  17. deep: true,
  18. immediate: true,
  19. }
  20. );

2️⃣ 侦听某个对象属性的变化

  1. vm.$watch(
  2. "msg.data",
  3. {
  4. handler() {
  5. },
  6. deep: true,
  7. immediate: true,
  8. }
  9. );

2️⃣ watch 多个属性

  1. <script>
  2. export default {
  3. data() {
  4. return {
  5. name: '',
  6. age: '',
  7. hobby: {
  8. aaa: '',
  9. bbb: '', // 不观察 bbb
  10. ccc: ''
  11. }
  12. }
  13. },
  14. computed: {
  15. watchMore() {
  16. const { name, age } = this
  17. const { aaa, ccc } = this.hobby
  18. return {
  19. name,
  20. age,
  21. aaa,
  22. ccc
  23. }
  24. }
  25. },
  26. watch: {
  27. watchMore(val) {
  28. console.log(val)
  29. }
  30. }
  31. }
  32. </script>

2️⃣ 当监听的数据的在初始不确定,由多个数据得到时,此时可以将第一个参数写成函数类型

  1. <!-- ********** HTML ********** -->
  2. <body>
  3. <div0 id="app">
  4. <h1>{{msg1}}</h1>
  5. <h1>{{msg2}}</h1>
  6. <button @click="add" type="button">Add</button>
  7. </div0>
  8. </body>
  9. // ********** JS/VUE **********
  10. const vm = new Vue({
  11. el: '#app',
  12. data: {
  13. msg1: 10,
  14. msg2: 20
  15. },
  16. methods: {
  17. add() {
  18. this.msg1++
  19. this.msg2++
  20. },
  21. }
  22. })
  23. vm.$watch(
  24. function () {
  25. // 表达式`this.msg1 + this.msg2`每次得出一个不同的结果时该函数都会被调用
  26. // 这就像监听一个未被定义的计算属性
  27. return this.msg1 + this.msg2;
  28. },
  29. {
  30. handler(newVal, oldVal) {
  31. // 每次 this.msg1 + this.msg2 得出一个不同的值时就会调用该函数
  32. console.log(newVal, oldVal);
  33. },
  34. deep: true,
  35. immediate: true,
  36. }
  37. );

2️⃣ 取消侦听函数

侦听器函数执行后,会返回一个取消侦听函数,用来停止触发回调:

  1. <!-- HTML -->
  2. <body>
  3. <div id="app">
  4. <h1>{{msg1}}</h1>
  5. <h1>{{msg2}}</h1>
  6. <button @click="add" type="button">Add</button>
  7. <button @click="cancel" type="button">Cancel</button>
  8. </div>
  9. </body>
  10. // JS/VUE
  11. const vm = new Vue({
  12. el: '#app',
  13. data: {
  14. msg1: 10,
  15. msg2: 20
  16. },
  17. methods: {
  18. add() {
  19. this.msg1++
  20. this.msg2++
  21. },
  22. cancel() {
  23. unwatch()
  24. }
  25. }
  26. })
  27. const unwatch = vm.$watch(
  28. function () {
  29. return this.msg1 + this.msg2;
  30. },
  31. {
  32. handler(newVal, oldVal) {
  33. console.log(newVal, oldVal);
  34. },
  35. deep: true,
  36. immediate: true,
  37. }
  38. );

使用 unwatch 时,需要注意的是,在带有 immediate 选项时,不能在第一次回调时取消侦听数据。

  1. // JS/VUE
  2. const unwatch = vm.$watch(
  3. 'msg',
  4. function () {
  5. unwatch(); // 此时会报错
  6. },
  7. {
  8. immediate: true
  9. }
  10. })

如果仍然希望在回调内部用一个取消侦听的函数,那么可以先检查该函数的可用性:

  1. // JS/VUE
  2. const vm = new Vue({
  3. el: '#app',
  4. data: {
  5. msg: 100,
  6. },
  7. methods: {
  8. add() {
  9. this.msg++
  10. },
  11. }
  12. })
  13. var unwatch = vm.$watch(
  14. 'msg',
  15. function () {
  16. if (unwatch) {
  17. unwatch();
  18. }
  19. },
  20. {
  21. immediate: true
  22. }
  23. );