模闆内的表達式非常便利,但是設計它們的初衷是用于簡單運算的。在模闆中放入太多的邏輯會讓模闆過重且難以維護。例如需要實作反轉字元串(比如輸入123,輸出的是321),使用“Mustache”文法 (雙大括号) ,是可以實作,但是不利于維護。
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>反轉字元串</h2>
<input type="text" v-model="msg">
<p> 反轉後的字元串:{{ msg.split('').reverse().join('') }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'hello'
}
})
</script>
</html>
通過上面的案例示範,發現模闆不再是簡單的聲明式邏輯。你必須看一段時間才能意識到,這裡是想要顯示變量 msg 的翻轉字元串。當你想要在模闆中多包含此處的翻轉字元串時,就會更加難以處理。
是以,對于任何複雜邏輯,你都應當使用計算屬性。
計算屬性——computed
基本用法:
- 在Vue執行個體化對象中設定
屬性,值是一個對象computed
- 在
對象裡面可以聲明多個計算屬性,計算屬性是一個函數,需要有computed
return
- 在插值表達式裡面使用計算屬性名
這裡使用計算屬性來實作上面的功能:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>反轉字元串</h2>
<input type="text" v-model="msg">
<!-- {{ }}裡面放的是一個計算屬性 -->
<p> 反轉後的字元串:{{ reversedMsg }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'hello'
},
computed: {
// 聲明一個計算屬性reversedMsg,它是一個函數,函數需要有return
reversedMsg: function(){
// 将字元串反轉後return
// this指向的是執行個體化對象vm
return this.msg.split('').reverse().join('');
}
}
})
</script>
</html>
最終得到的結果是一樣的,但是很明顯的可以看出插值表達式裡面的内容就很簡單了。
解析:以上代碼中,聲明了一個計算屬性reversedMsg,提供的函數将用作屬性 vm.reversedMsg 的 getter,vm.reversedMsg 依賴于 vm.msg,在 vm.msg 發生改變時,vm.reversedMsg也會更新。
computed 與 methods 的差別
這時,你可能注意到我們可以通過在插值表達式裡面調用方法來實作同樣的效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>反轉字元串</h2>
<input type="text" v-model="msg">
<p> 反轉後的字元串:{{ reversedMsg() }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'hello'
},
methods: {
reversedMsg: function(){
return this.msg.split('').reverse().join('');
}
}
})
</script>
</html>
兩種方式的最終結果确實是完全相同的。
然而,不同的是計算屬性是基于它們的響應式依賴進行緩存的。隻在相關響應式依賴發生改變時它們才會重新求值。這就意味着隻要 msg還沒有發生改變,多次通路 reversedMsg 計算屬性會立即傳回之前的計算結果,而不必再次執行函數。
我們為什麼需要緩存?
假設我們有一個性能開銷比較大的計算屬性 A,它需要周遊一個巨大的數組并做大量的計算。然後我們可能有其他的計算屬性依賴于 A。如果沒有緩存,我們将不可避免的多次執行 A 的 getter!如果你不希望有緩存,請用方法來替代。
使用計算屬性實作驗證使用者名的功能
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>computed驗證使用者名</h2>
<hr>
<p>使用者名:<input type="text" name="" id="" v-model="username"><span>{{ message }}</span></p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
title: 'watch',
username: ''
},
computed: {
message: function(){
if(!this.username){
return '請輸入使用者名'
}
if(this.username.length < 5){
return '使用者名長度不能少于5個字元'
}
if(this.username.length > 12){
return '使用者名長度不能多于12個字元'
}
return ''
}
}
})
</script>
</html>
偵聽器——watch
雖然計算屬性在大多數情況下更合适,但有時也需要一個自定義的偵聽器。這就是為什麼 Vue 通過 watch 選項提供了一個更通用的方法,來響應資料的變化。當需要在資料變化時執行異步或開銷較大的操作時,這個方式是最有用的。
基本用法:
- 在Vue執行個體化對象裡面設定
屬性,值是一個對象watch
- 在
對象中可以監聽某個資料模型的變化,接收的值是一個函數watch
- 函數可以接收該資料模型的新值和舊值
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>偵聽器——watch</h2>
<p>變資料模型msg的值:{{ msg }}</p>
<p>資料模型msg的值:<input type="text" v-model="msg"></p>
<hr>
<p>舊值:{{ oldMsg }}</p>
<p>新值:{{ newMsg }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: '',
oldMsg: '',
newMsg: ''
},
watch: {
msg: function(newMsg, oldMsg){
this.oldMsg = oldMsg;
this.newMsg = newMsg;
console.log('舊值:' + oldMsg + '新值:' + newMsg);
}
}
})
</script>
</html>
當我們在文本框裡面輸入一個字元的時候,這個字元串就成為了新值,這時候我們可以看到下面或者控制台顯示新值和舊值,第一次的時候,舊值本來就是空的。
當再次輸入第二個字元時,第一次輸入的字元就成了舊值
使用偵聽器實作使用者名驗證
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>watch驗證使用者名</h2>
<hr>
<p>使用者名:<input type="text" name="" id="" v-model="username"><span>{{ message }}</span></p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
title: 'watch',
username: '',
message: ''
},
watch: {
username: function(newVal, oldVal){
console.log(newVal, oldVal);
if(!newVal){
this.message = '請輸入使用者名'
return
}
if(newVal.length < 5){
this.message = '使用者名長度不能少于5個字元'
return
}
if(newVal.length > 12){
this.message = '使用者名長度不能多于12個字元'
return
}
this.message = ''
}
}
})
</script>
</html>
上面我們說到,執行異步操作的時候,建議使用
watch
,下面示範:
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>驗證使用者名是否可用</h2>
<hr>
<p>使用者名:<input type="text" name="" id="" v-model.lazy="username"><span>{{ tip }}</span></p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
/*
1、使用偵聽器監聽敏使用者名的變化
2、調用背景接口進行驗證
3、根據驗證結果調整提示資訊
*/
var vm = new Vue({
el: '#app',
data: {
username: '',
tip: ''
},
methods: {
checkName: function(username){
// 調用接口,使用延時器模拟背景接口的調用
var that = this;
setTimeout(function(){
// 模拟接口調用
if(username == 'admin'){
that.tip = '使用者名已存在';
}else{
that.tip = '使用者名可以使用';
}
}, 2000)
}
},
watch: {
username: function(val){
// 調用背景接口進行合法性驗證
this.checkName(val);
// 修改提示資訊
this.tip = '正在驗證...';
}
}
})
</script>
</html>
計算屬性和偵聽器的差別
差別一:
watch适合處理的場景是,偵聽一個數的變化,當該資料變化,來處理其他與之相關資料的變化(該資料影響别的多個資料)
computed适合處理的場景是,獲得一個值或者結果,該結果受其他的依賴的影響。(一個資料受多個資料影響)
差別二:
watch不需要傳回值,computed需要傳回值。
計算屬性中,傳回的是一個data中沒有的值(新值)并且必須包含return。
偵聽器中,變化的值是data中存在的值,并且不包含return 在偵聽器中可以執行異步操作,并控制操作的頻率,這些都是計算屬性無法做到的。
差別三:
watch可以處理異步操作,computed則無法執行異步操作