不只懂 Vue 语法:试说明 computed 的 get 与 set 运作机制?

问题回答

computed 有 getter(取值) 和 setter (写入值)可使用,但预设只会有 getter 使用,因此 computed 预设是唯读,不能写入值。每次建立元件时,都会先跑一次 computed 里的 getter 来取得此 computed 函式里的值。之後如果该 computed 函式里的依赖没有变化,就不会再跑此 computed 函式,即是不会跑 此函式的getter,并只会一直回传之前缓存起来的值。

另外,如果要使用 setter,我们可以透过在 computed 属性里建立物件的方法来建立 gettersetter

以下会再作详细解说。

computed 只是唯读,因为预值只有 getter

预设 computed 只有 getter,意思是我只能透过 computed 函式 来取值。

例如我尝试在 mounted,触发 total 函式里的 setter

<div id="app">
  <p> {{ total }} </p>
</div>
data() {
    return {
      price: 100,
      discount: 0.8
    }
},
computed: {
    total() {
      return this.price * this.discount
    }
},
mounted() {
    this.total = 1000        
}

报错:

[Vue warn]: Write operation failed: computed property 'total' is readonly.

自行加入 setter

Vue 官网有提到,虽然预设只有 getter 可使用,但我们可以使用物件,并在里面使用 getter 和 setter。

当你打开 console 查看会看到其实 computed 属性(以 total 为例)也会有 set

以下例子示范如何使用预设没有的 setter。注意:需要使用物件来建立 gettersetter

<div id="app">
  <p> 定价: {{ price }} </p>
  <p>折扣:{{ discount }} </p>
  <p>折扣价:{{ total }}</p>
  <button @click="changeComputed">按此随机产生折扣</button>
</div>
  data() {
    return {
      price: 100,
      discount: 0.5
    }
  },
  computed: {
    // 需使用物件
     total: {
       get() {
         console.log('触发 getter!')
         return this.price * this.discount
       },
       set(newVal) {
         console.log('触发 setter!')
         this.discount = newVal
       }
     }
  },
  methods: {
    changeComputed() {
      // 触发 set,再触发 get
      this.total = Number(Math.random().toFixed(1))
    }
  }

结果:

程序码:
https://codepen.io/alysachan/pen/QWgojov

this.total = Number(Math.random().toFixed(1)) 这行虽然看来好像怪怪的?看似是直接把 total 改为随机产生出来的 discount。但事实上,这里所做的事是触发 set 函式,并非修改 total 的值。

重温一下原生 JavaScript 里 set(setter) 的用法:

const obj = {
    num: 0,
    get add(){
        return this.num
    },
    set add(newVal){
        this.num += newVal
    }
}
// 使用 get
console.log(obj.add) // 0
// 使用 set
console.log(obj.add = 100) //100

以上 obj.add = 100,就是把 100 带进去 add 函式里,并非改掉 add 函式所回传的值。

因此,回到 Vue 的例子:this.total = Number(Math.random().toFixed(1))。就是把右边的值,带进去 totalset 函式里,也即是 newVal 参数的值。并且修改了 discount 的值。

但为什麽画面明明显示total 的值被修改了?因为在 computed 函式里,所有用到的 data 资料,都会是该函式的依赖。因此当 this.discount 有变化时,就会触发 total 的更新。

所以整个运作次序就是:

  1. 按按钮触发 changeComputed
  2. 触发 total 里的 set,修改 discount
  3. 触发 total 更新值

打开 console 会看到,先触发 setter,之後才是 getter:

注意,触发了 set 不代表一定会触发 get。如果在 set 里没有修改到 get 里的任何一个依赖,get 就不会被触发,即是不会更新在画面中 total 的值。例如我只是在 set 里印出参数:

total: {
    get() {
         return this.price * this.discount
    },
    set(newVal) {
         console.log(newVal)
    }
}

但画面没用到 computed,即使依赖有变,也不会执行 getter?

如果画面中没有用到 total,只剩下 pricediscount

<div id="app">
  <p> 定价: {{ price }} </p>
  <p>折扣:{{ discount }} </p>
  <!--   <p>折扣价:{{ total }}</p> -->
  <button @click="changeComputed">按此随机产生折扣</button>
</div>

这情况里,即使按按钮,修改了 this.discount,也不会触发 total 里的 get。反之,只会一直印出 触发 setter! 文字。除非你打开了 Vue devtool 检查工具看一次,之後 Vue 才会执行 getter,并印出 触发 getter! 文字。

因此可以总结一句,如果在画面没用到此 computed 函式,而且没有在其他程序码里有使用过此 computed 函式的值。即代表不会跑该 computed 函式里的 get,也就是没有更新此值。

不过这情况不用怎样担心,即是如果在画面上没用过此值,但你在其他程序码用到时,还是会写 this.total。一旦写了 this.total,就会触发 total 的 getter,也就一定会取得最新的值。

验证缓存机制

当然,另一个情况就是当依赖的资料没变化,就不会执行 getter,并且会一直回传之前缓存起来的值,也就是 computed 的缓存特性。

例如改一下之前的例子,按按钮後,把 discount 改为 0.5。结果只会一直印出「触发 setter!」,因为资料里的 discount 也是 0.5,结果就没变化:

methods: {
    changeComputed() {
      // 只会触发 set
      // 一直都是改为 0.5
      this.total = 0.5
    }
},

总结

  • computed 预设只使用 getter,没有 setter。因此 computed 函式只会是唯读。
  • 可以自行在 computed 里加入 setter,但注意要以物件型别来写,并在里面加入 gettersetter
  • 如果画面没用到该 computed 函式的值,而在程序码中也没有用过此值,Vue 就不会跑该函式里的 getter,也不知道会回传什麽值。

参考资料

那些关於 Vue 的小细节 - Computed 中 getter 和 setter 触发的时间点
Vue 笔记 - Computed 的 get() 与 set()
属性的 getter 和 setter


<<:  Swift纯Code之旅 Day20. 「ViewController好乱(2) - MVC画面分离」

>>:  【Day14】样式 Style

day11 Kotlin coroutine 花生什麽事?

前面我讲10篇了,告诉你们coroutine是什麽,怎麽用,如何切thread,和她背後发生什麽事 ...

Swift纯Code之旅 Day1. 「前置作业」

这次的挑战赛并不是什麽特别难的目标,由於我是转职写Swift的,因此也想写些比较基础入门的资讯提供...

[今晚我想来点 Express 佐 MVC 分层架构] DAY 28 - node.js 与线程 (上)

node.js 之所以能够运行 JavaScript 程序码,是因为底层依赖 google 在 ch...

30天学会 Python-Day24: 影像处理

OpenCV 用於处里影像的套件,除了图片外还能处理影片 读取影像 用 cv2.imread() 可...

Frontend, and frameworks

前言 2020 秋天,我将用 30 天的时间,来尝试回答和网路前端开发相关的 30 个问题。30 ...