一、概念
1.computed
Vue中用计算属性来描述依赖响应式状态的复杂逻辑.模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象:
constauthor=reactive({
name:'JohnDoe',
books:[
'Vue2-AdvancedGuide',
'Vue3-BasicGuide',
'Vue4-TheMystery'
]
})
我们想根据author是否已有一些书籍来展示不同的信息:
<p>Haspublishedbooks:</p>
<span>{{author.books.length>0?'Yes':'No'}}</span>
这里的模板看起来有些复杂。我们必须认真看好一会儿才能明白它的计算依赖于author.books。更重要的是,如果在模板中需要不止一次这样的计算,我们
可不想将这样的代码在模板里重复好多遍。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。这是重构后的示例:
<scriptsetup>
import{reactive,computed}from'vue'
constauthor=reactive({
name:'JohnDoe',
books:[
'Vue2-AdvancedGuide',
'Vue3-BasicGuide',
'Vue4-TheMystery'
]
})
//一个计算属性ref
constpublishedBooksMessage=computed(()=>{
returnauthor.books.length>0?'Yes':'No'
})
</script>
<template>
<p>Haspublishedbooks:</p>
<span>{{publishedBooksMessage}}</span>
</template>
我们在这里定义了一个计算属性publishedBooksMessage。computed()方法期望接收一个getter函数,返回值为一个计算属性ref。和其他一般的ref类似,你可以通过publishedBooksMessage.value访问计算结果。计算属性ref也会在模板中自动解包,因此在模板表达式中引用时无需添加.value。
Vue的计算属性会自动追踪响应式依赖。它会检测到publishedBooksMessage依赖于author.books,所以当author.books改变时,任何依赖于publishedBooksMessage的绑定都会同时更新。
2.Watch
在Vue中watch代表侦听器,我们可以使用watch函数在每次响应式状态发生变化时触发回调函数计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改DOM,或是根据异步操作的结果去修改另一处的状态。
<scriptsetup>
import{ref,watch}from'vue'
constquestion=ref('')
constanswer=ref('Questionsusuallycontainaquestionmark.;-)')
//可以直接侦听一个ref
watch(question,async(newQuestion,oldQuestion)=>{
if(newQuestion.indexOf('?')>-1){
answer.value='Thinking...'
try{
constres=awaitfetch('https://yesno.wtf/api')
answer.value=(awaitres.json()).answer
}catch(error){
answer.value='Error!CouldnotreachtheAPI.'+error
}
}
})
</script>
<template>
<p>
Askayes/noquestion:
<inputv-model="question"/>
</p>
<p>{{answer}}</p>
</template>
侦听数据源类型
watch的第一个参数可以是不同形式的“数据源”:它可以是一个ref(包括计算
属性)、一个响应式对象、一个getter函数、或多个数据源组成的数组:
constx=ref(0)
consty=ref(0)
//单个ref
watch(x,(newX)=>{
console.log(`xis${newX}`)
})
//getter函数
watch(
()=>x.value+y.value,
(sum)=>{
console.log(`sumofx+yis:${sum}`)
}
)
//多个来源组成的数组
watch([x,()=>y.value],([newX,newY])=>{
console.log(`xis${newX}andyis${newY}`)
})
注意,你不能直接侦听响应式对象的属性值,例如:
constobj=reactive({count:0})
//错误,因为watch()得到的参数是一个number
watch(obj.count,(count)=>{
console.log(`countis:${count}`)
})
这里需要用一个返回该属性的getter函数:
//提供一个getter函数
watch(
()=>obj.count,
(count)=>{
console.log(`countis:${count}`)
}
)
深层侦听器
直接给watch()传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:
constobj=reactive({count:0})
watch(obj,(newValue,oldValue)=>{
//在嵌套的属性变更时触发
//注意:`newValue`此处和`oldValue`是相等的
//因为它们是同一个对象!
})
obj.count++
相比之下,一个返回响应式对象的getter函数,只有在返回不同的对象时,才会触发回调:
watch(
()=>state.someObject,
()=>{
//仅当state.someObject被替换时触发
}
)
你也可以给上面这个例子显式地加上deep选项,强制转成深层侦听器:
watch(
()=>state.someObject,
(newValue,oldValue)=>{
//注意:`newValue`此处和`oldValue`是相等的
//*除非*state.someObject被整个替换了
},
{deep:true}
)
即时回调的侦听器
watch默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。我们可以通过传入immediate:true选项来强制侦听器的回调立即执行:
watch(source,(newValue,oldValue)=>{
//立即执行,且当`source`改变时再次执行
},{immediate:true})
二、两者的区别
跟据两个技术点的语法用途和设计原理主要有以下区别:
1.计算属性有缓存机制,侦听器没有
2.计算属性不支持异步操作,侦听器支持
3.计算属性可以给vue新增属性,侦听器必须是data中已有的属性
4.计算属性只要使用了就会立即执行一次,侦听器默认只有当数据第一次改
变才会执行,需要设置immediate属性来控制是否立即执行一次
5.计算属性函数一定要有返回值,侦听器不用写返回值
6.计算属性是解决模板语法冗余,而侦听器是监听某一个数据的变化