简短答法:computed 最大特点是必须回传一个值,并且会把它缓存起来,当函式里的依赖改变时,才会重新执行和求值。但 watch 与 methods 不会强制要求回传一个值,它们只需执行动作,不一定要回传值。watch 会侦测单一个值,当它有变化时就执行。至於 methods,只要呼叫它,它就会执行,但 computed 和 watch 则不是透过呼叫来执行。
以下会再作详细解说。
computed 属性的最明显特点:
this.某computed 函式 = 123
会报错。最後两点很直白,以下就不作解释。第 2 点会涉及到 computed 本身的 getter 机制,明天的文章才会针对作说明。
因此以下先解释第一点。
看看 Vue 官方执行 computed 的解释:
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
什麽是响应式依赖?意思是在一个 computed 函式里,它所用到在 data
建立的资料。
一旦这些资料有变化,这个 computed 函式就会被重新执行和求值。
举例就,以下 total
的依赖就是 this.price
, this.quantity
, this.discount
。只要其中一样有变化,都会重新执行 total,并回传新的值:
computed:{
total(){
return this.price * this.quantity * this.discount
}
}
以下情况,如果 greeting
有变化,会否触发 total?
computed:{
total(){
console.log(this.greeting)
return this.price * this.quantity * this.discount
}
}
会。即使 greeting
并没有用来计算 total
,但它也是在 total
这个 computed 函式内,因此也是其中一个依赖。
再看一个例子,如果 quantity 有变化,以下会否触发 total?
let quantity = 1;
computed:{
total(){
return this.price * quantity * this.discount
}
}
不会。因为 quantity
不是在 data
里建立,Vue 不会侦察到 quantity
的变化。所以不会触发 total
。
关於 Vue 如何侦测到 data 资料的变化,可参考此系列之前的文章:
Vue 2:在 Vue 2 为何无法直接修改物件型别资料里的值?
Vue 3:Vue 3 如何使用 Proxy 实现响应式(Reactivity)?
意思是当 computed 里的所有依赖都没发生变化,此 computed 函式就会一直回传之前储存起来的值。
举例说,我按下按钮就会把 num
变成 1。
<div id="app">
<button @click="num = 1">按我改num</button>
<p> 用add方法把以下的值由0变1:</p>
<p> {{ add }} </p>
</div>
data(){
return{
num: 0,
}
},
computed:{
add(){
console.log('我有被触发了!')
return this.num
}
}
刚建立元件时,会印出 「我有被触发了!」。
然後,第一次按下按钮後, num
变成 1,会印出「我有被触发了!」。但之後按按钮,add
就不会再触发,而「我有被触发了!」这句也不会印出来。
因为每次按按钮,都同样是把 num
修改为 1,跟之前储存起来的数值 1 相比并没有变化。因此 add
不会被执行,而画面只会一直显示 1 这个之前已缓存起来的值。
程序码:
https://codepen.io/alysachan/pen/LYWmOLq?editors=1111
如果用以下做法修改 computed 的值就会报错:
computed:{
num(){
return 1
}
},
mounted() {
this.num = 10
}
报错:
[Vue warn]: Write operation failed: computed property "num" is readonly.
因为 computed 的运作原理只有 getter (取值)没有 setter(存值)。因此 computed property 只是唯读。
这部分的解说内容会比较多,留待明天的文章才针对作解释。
methods 的概念简单很多。跟平常我们写 JavaScript 时使用函式的概念一样。当它被呼叫时就会执行,可带入参数,而结果不一定要回传一个值,只是执行一些动作也可以。
跟 computed 明显不同,一旦 methods 被呼叫,它不管函式里的所有依赖有没有变化,也会照样执行。
重用以上例子,如果使用 methods 来印出文字:
<div id="app">
<button @click="add">按我改num</button>
<p> 用add方法把以下的值由0变1:</p>
<p> {{ num }} </p>
</div>
data(){
return{
num: 0,
}
},
methods: {
add(){
console.log('我是用method,我有被触发了!');
this.num = 1;
}
}
程序码:
https://codepen.io/alysachan/pen/BaWxmYz?editors=1111
跟先前的 computed 示范例子的不同:
console
不会先印出一次文字。num
变成 1 後再按按钮,console
还是会印出文字。看看以下例子:
<div id="app">
<label for="name">输入名称:</label>
<input type="text" v-model="name" id="name">
<p> {{ greetUserComputed }} </p>
<p> {{ greetUserMethods() }} </p>
</div>
data(){
return{
greeting: '您好!',
name: 'Alysa'
}
},
computed: {
greetUserComputed() {
return `${this.name} ${this.greeting}`
}
},
methods:{
greetUserMethods(){
return `${this.name} ${this.greeting}`
}
}
程序码:
https://codepen.io/alysachan/pen/QWgYGWP?editors=1011
结果是,当我在 input 栏位输入内容,不论 methods 还是 computed 都有被执行。我们知道 computed 一定会被执行,因为依赖出现变化。但为什麽 methods 都会被执行?
Vue 官方文件说明,如果在模版里呼叫 methods 函式,而该函式里的有使用在 data 里建立的资料,一旦这些资料出现变化,就会执行此 methods 函式。
截图自 Vue 官网
因此,在画面呼叫 methods 函式的话,一旦依赖有变化,就会被重新执行。
前两点都很直白,但对於最後一点的说法,透过例子会更易理解。
computed
无法进行非同步工作,以 AJAX 取资料为例,下面例子是分别以 computed 和 watch 打 API 取得一笔 User 资料。
结果 computed 会回传 [object Promise]
。至於 watch,预设是绑定一个空物件,当我按按钮去取资料时,最後就会显示 User 的资料:
<div id="app">
<button type="button" @click="toggleBtn = true">
取得 User
</button>
<p> {{ userComputed }} </p>
<p> {{ user }} </p>
</div>
data(){
return{
user: {},
toggleBtn: false,
}
},
computed:{
async userComputed() {
try {
const res = await axios.get('https://randomuser.me/api/')
return res.data.results[0]
}catch(error){
console.log(error)
}
}
},
watch: {
async toggleBtn() {
this.user = 'loading...'
try {
const res = await axios.get('https://randomuser.me/api/')
this.user = res.data.results[0]
}catch(error){
console.log(error)
}
}
}
程序码:
https://codepen.io/alysachan/pen/ZEywejd?editors=1011
结果:
immediate
、deep
属性immediate
:预设 watch 的函式需要等待它所侦测的值变动时,才会执行。但如果想一载入元件就执行,可以设定 immediate: true
。deep
:当要侦测物件里的属性的变动时,需使用 deep: true
。示范:
<div id="app">
<label for="name">输入名称:</label>
<input type="text" v-model="user.name" id="name">
</div>
data(){
return{
user: {
name: 'Alysa',
job: 'web developer'
}
}
},
watch: {
user: {
handler(newVal, oldVal){
console.log(oldVal)
console.log(newVal)
},
deep: true,
// 当元件建立好,就立即执行一次此函式
immediate: true
},
// 无法侦测物件里属性的变动
// user(newVal, oldVal) {
// console.log(oldVal)
// console.log(newVal)
// }
// 以下是错误写法
// user(newVal, oldVal) {
// deep: true,
// immediate: true,
// console.log(oldVal)
// console.log(newVal)
// }
}
程序码:
https://codepen.io/alysachan/pen/yLXZbeB?editors=1010
结果是,当使用了 deep 和 immediate 属性,刚载入画面时,console 就会印出数值。之後当我在 input 栏位输入内容,watch 也能侦测到属性的变动,因此再次印出数值。
注意,当你输入了内容,会发现 newVal 和 oldVal 都是一样,因为物件是 pass by reference,所以新旧值都会是相同。
你会发现,有些情况即使使用 computed、watch 和 methods 都能实现同一效果。但是,computed
的效能通常都会比较好,因为:
第一点,官方例子有清晰说明:
截图自 Vue 官网
当使用 watch,就要建立两个函式。反之, 只需建立一个 computed 函式就能完成同样功能。
第二点,因为当依赖没变化时,computed 不会执行,直接回传缓存的值。假设该 computed 函式里每次执行都要跑一个极长的阵列资料,最後回传一笔处理好的阵列。每次执行时会效能耗损都不少。对比起只要一呼叫,就一定会执行的 methods,computed 的缓存机制就比较有利。
computed | methods | |
---|---|---|
如果computed 的响应式依赖没有改动,就不会触发。 |
每次触发methods ,methods 里的函式一定会执行 |
|
有缓存资料的功能 | 没有缓存资料的功能 | |
不可带入参数 | 可带入参数 | |
可在HTML里直接使用该computed函式所回传的值,因为computed函式本身是有get函式,最後一定会回传一个值 | methods本身没有规定一定要回传一个值 |
注意:
this.computed 函式 = 123
来修改 computed 函式的值。computed | watch |
---|---|
侦测到资料变动时,会回传资料 | 侦测到资料变动时,不会回传值,只会执行工作 |
能侦测多个值的变动(只要该资料是在该computed函式里和data属性里) | 只能侦测一个值 |
不能带入参数 | 能带入参数,第一个参数是新值,第二个参数是旧值 |
Vue 的 computed、methods 跟 watch 差在哪里?(上)
认识 Vue.js watch 监听器
<<: TailwindCSS 从零开始 - 自订 addBase 套件
To use Azure Machine Learning Create an Azure Mach...
前面我们有大概提到Enzyme的优点及作用~ 这篇我们要直接来安装Enzyme和导入Enzyme来供...
Which is better? Modification or construction? 虽然...
假设收到一个回馈, 希望能即时把目前的点数状况反应在操作画面上, 让调度员可以随时掌握点数的状况来做...
Zsh 可以让使用者利用配置客制各种不同的功能,像是命令的自动补全、提示、高亮与缩写等。但是要自己设...