前言
進階模板語法詳細介紹
本節內容包含下述子章節:
- 模板資料細節說明
- 動態切換 ClassName 及 Style 多種方法
- v-for 與其使用細節
- v-if 與其使用細節
- Computed 與 Watch
- 表單細節操作
- v-on 的頁面操作細節
- 章節作業
模板資料細節說明
- 在
UI上顯示變數內容,可在直接使用兩個花括號包覆變數即可顯示,
並且在 input 使用 v-model="text" 進行雙向綁定。
<div id="app">
<h4>字串</h4>
{{ text }}
<input type="text" class="form-control" v-model="text">
<div>
<script>
var app = new Vue({
el: '#app',
data: {
text: '這是一段文字',
},
});
</script>
- 在
Vue中無法直接使用兩個花括號插入一段HTML語法,
如: data 裡 rawHtml: '<span class="text-danger">紅色文字</span>' ,再將 放在 html
<div id="app">
<h4 class="mt-3">原始 HTML</h4>
{{ rawHtml }}
<div>
<script>
var app = new Vue({
el: '#app',
data: {
},
});
</script>
該語法會直接轉成純文字後顯示,像這樣
若要插入 HTML 語法請使用 v-html 再將變數 rawHtml 帶入,即 v-html="rawHtml"
<div id="app">
<h4 class="mt-3">原始 HTML</h4>
<p v-html="rawHtml">請在此加入原始 HTML 結構</p>
<div>
<script>
var app = new Vue({
el: '#app',
data: {
},
});
</script>
結果就會是這樣
注意!
在網站上動態渲染任意HTML是非常危險的,因為容易導致XSS攻擊。因此只在可信內容上使用v-html,
永不用在使用者提交的內容(例如:使用者留言)上。
跨網站指令碼 (英語:
Cross-site scripting,通常簡稱為:XSS) 是一種網站應用程式的安全漏洞攻擊,是代碼注入的一種。它允許惡意使用者將程式碼注入到網頁上,其他使用者在觀看網頁時就會受到影響。
- 字串除了雙向綁定之外,也可以進行單向綁定,只需要在標籤上新增
v-once,這樣就只會輸出一次。
<div id="app">
<h4 class="mt-3">單次綁定</h4>
<div v-text="text" v-once >請將此欄位改為單次綁定</div>
<div>
<script>
var app = new Vue({
el: '#app',
data: {
text: '這是一段文字',
},
});
</script>
- 在兩個花括號裡也可以輸入表達式,也就是說可以撰寫
javascript讓它回傳一個值。
<div id="app">
<h4 class="mt-3">表達式</h4>
<p>練習不同的表達式</p>
<p>{{ text + rawHtml }}</p> <!-- 文字相加 -->
<p>{{ text.split('').reverse().join('') }}</p> <!-- 文字反轉 -->
<p>{{ number1 + number2 }}</p> <!-- 數字相加 -->
<div>
<script>
var app = new Vue({
el: '#app',
data: {
text: '這是一段文字',
number1: 100,
number2: 300,
htmlId: 'HTMLID',
isDisabled: true
},
});
</script>
- 透過資料狀態來操作
HTML屬性,使用v-bind綁定id,即v-bind:id="htmlId",也可縮寫:id="htmlId"。
除了可以動態操作 class id 之外,也可以操作 input 的狀態,使用 v-bind 綁定 disabled 。
<div id="app">
<h4 class="mt-3">HTML 屬性</h4>
<p v-bind:id="htmlId">請綁定上 ID</p>
<input type="text" :disabled="isDisabled" class="form-control" placeholder="請在此加上動態 disabled">
<div>
<script>
var app = new Vue({
el: '#app',
data: {
text: '這是一段文字',
number1: 100,
number2: 300,
htmlId: 'HTMLID',
isDisabled: true
},
});
</script>
動態切換 ClassName 及 Style 多種方法
- 直接傳入物件
假設我們要為一個 box 加入動態的 class name ,並且 class name 可以做切換,我們可以使用 :class 後面插入
一個物件 { } ,物件前方的值放 class name,後方放可以切換的值,即 :class="{ 'rotate' : isTransform}"
<style>
.box {
transition: all .5s; /* 漸變效果 */
}
.box.rotate {
transform: rotate(45deg) /* 旋轉 */
}
</style>
<div id="app">
<h4>物件寫法</h4>
<div class="box" :class="{ 'rotate' : isTransform}"></div>
<p>請為此元素加上動態 className</p>
<hr>
<button class="btn btn-outline-primary" v-on:click="isTransform = !isTransform">選轉物件</button>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="classToggle1" v-model="boxColor">
<label class="form-check-label" for="classToggle1">切換色彩</label>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
isTransform: false,
boxColor: false,
},
});
</script>
當然它因為是物件的關係我們可以插入一個以上的值,所以我們可以再將 'bg-danger' : boxColor 插入
'rotate' : isTransform 後方,即 :class="{ 'rotate' : isTransform, 'bg-danger' : boxColor}" ,
這時候就可以同時旋轉物件以及變換顏色。
<style>
.box {
transition: all .5s; /* 漸變效果 */
}
.box.rotate {
transform: rotate(45deg) /* 旋轉 */
}
</style>
<div id="app">
<h4>物件寫法</h4>
<div class="box" :class="{ 'rotate' : isTransform, 'bg-danger' : boxColor}"></div>
<p>請為此元素加上動態 className</p>
<hr>
<button class="btn btn-outline-primary" v-on:click="isTransform = !isTransform">選轉物件</button>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="classToggle1" v-model="boxColor">
<label class="form-check-label" for="classToggle1">切換色彩</label>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
isTransform: false,
boxColor: false,
},
});
</script>
- 傳入物件變數
將欲傳入 class 的物件,定義為一個物件變數,再將變數傳入 class 中。
首先我們在 vue 的 data 裡先將 'rotate': false, 及 'bg-danger': false, 用物件的方式定義好名稱 objectClass ,
再將 objectClass 名稱綁定在 html 的 class 上,即 :class="objectClass" 。
<style>
.box {
transition: all .5s; /* 漸變效果 */
}
.box.rotate {
transform: rotate(45deg) /* 旋轉 */
}
</style>
<div id="app">
<h5>物件寫法 2</h5>
<div class="box" :class="objectClass"></div>
<hr>
<button class="btn btn-outline-primary">選轉物件</button>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="classToggle2">
<label class="form-check-label" for="classToggle2">切換色彩</label>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
isTransform: false,
boxColor: false,
objectClass: {
'rotate': false,
'bg-danger': false,
},
},
});
</script>
可是現在遇到一個問題,我們雖然將樣式套用了,那要怎麼切換它?
切換旋轉可以在
button標籤用v-on綁定click事件,即v-on:click="objectClass.rotate = !objectClass.rotate"。切換顏色可以在
input標籤用v-model綁定,即v-model="objectClass.bg-danger",但是這邊要注意console可能
會跳錯,顯示 danger is not defined ,原因是因為我們不能使用 dash 符號 - 來做為物件選取的名稱,我們必須用中括號
[ ] 的方式來選取 bg-danger ,即 v-model="objectClass['bg-danger']" 。
<style>
.box {
transition: all .5s; /* 漸變效果 */
}
.box.rotate {
transform: rotate(45deg) /* 旋轉 */
}
</style>
<div id="app">
<h5>物件寫法 2</h5>
<div class="box" :class="objectClass"></div>
<hr>
<button class="btn btn-outline-primary" v-on:click="objectClass.rotate = !objectClass.rotate">選轉物件</button>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="classToggle2" v-model="objectClass['bg-danger']">
<label class="form-check-label" for="classToggle2">切換色彩</label>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
isTransform: false,
boxColor: false,
objectClass: {
'rotate': false,
'bg-danger': false,
},
},
});
</script>
- 陣列寫法
適用於 class name 長度比較不確定的狀況。
在套用 class name 時有很多可以寫入,例如:btn-outline-primary 、 active ,接下來我們動態加上 class可以用陣列的
方式,用中括號 [ ] 將 btn-outline-primary 和 active放入,即 :class="['btn-outline-primary', 'active']" 這個
就是陣列的形式。
<div id="app">
<h4>陣列寫法</h4>
<button class="btn" :class="['btn-outline-primary', 'active']">請操作本元件</button>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="classToggle3" >
<label class="form-check-label" for="classToggle3">切換樣式</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="classToggle4" >
<label class="form-check-label" for="classToggle4">啟用元素狀態</label>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
isTransform: false,
boxColor: false,
objectClass: {
'rotate': false,
'bg-danger': false,
},
},
});
</script>
我們也可以把 array 的部份拉出拉獨立成為一個變數。
先在 Vue 定義好 arrayClass 的空陣列,將 arrayClass 名稱加入 button 的動態 class ,即 :class="arrayClass" 。
接下來我們透過下方的 check box 一個一個把 class 加上。
切換樣式:
在input標籤加上v-model="arrayClass"及value="btn-outline-primary"啟用元素狀態
在input標籤加上v-model="arrayClass"及value="active"
<div id="app">
<h4>陣列寫法</h4>
<button class="btn" :class="arrayClass">請操作本元件</button>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="classToggle3" v-model="arrayClass" value="btn-outline-primary">
<label class="form-check-label" for="classToggle3">切換樣式</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="classToggle4" v-model="arrayClass" value="active">
<label class="form-check-label" for="classToggle4">啟用元素狀態</label>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
isTransform: false,
boxColor: false,
objectClass: {
'rotate': false,
'bg-danger': false,
},
// Array 操作
arrayClass: [],
},
});
</script>
- 綁定行內樣式
插入行內樣式我們是使用冒號 : 加上 style ,接著使用物件 { } 的方式,前面放「樣式屬性」,後方放「樣式的值」,
即 :style="{backgroundColor: 'red' }"
注意原先
CSS是使用中線命名規則,要改成駝峰式,例如background-color改成backgroundColor
物件也可以存為變數來使用,我們在 Vue 裡面定義了 class name styleObject,將這個 styleObject 直接綁定在
style 即可。
接著我們還可以使用陣列的方式來撰寫,在 [ ] 裡面我們要插入完整的物件,像這樣
:style="[{backgroundColor: 'red' }, {borderWidth: '5px' }]" 或是 :style="[styleObject, styleObject2]"
<div id="app">
<h4>綁定行內樣式</h4>
<div class="box" :style="{backgroundColor: 'red' }"></div>
<div class="box" :style="styleObject"></div>
<div class="box" :style="[styleObject, styleObject2]"></div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
isTransform: false,
boxColor: false,
objectClass: {
'rotate': false,
'bg-danger': false,
},
// Array 操作
arrayClass: [],
// 行內樣式
// 使用駝峰式命名
styleObject: {
backgroundColor: 'red',
borderWidth: '5px'
},
styleObject2: {
boxShadow: '3px 3px 5px rgba(0, 0, 0, 0.16)'
},
},
});
</script>
- Prefix
vue 在動態加上這些樣式時,我們是不需要手動加上 Prefix ,它會自動依照每個瀏覽器版本需求自動加上。
<div id="app">
<h5>自動加上 Prefix (每個版本結果不同)</h5>
<div class="box" :style="styleObject3"></div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
isTransform: false,
boxColor: false,
objectClass: {
'rotate': false,
'bg-danger': false,
},
// Array 操作
arrayClass: [],
// 行內樣式
// 使用駝峰式命名
styleObject: {
backgroundColor: 'red',
borderWidth: '5px'
},
styleObject2: {
boxShadow: '3px 3px 5px rgba(0, 0, 0, 0.16)'
},
styleObject3: {
userSelect: 'none'
}
},
});
</script>
v-for 與其使用細節
在前面我們有稍微提到了 v-for,現在來複習一下,一樣先建構 Vue 的基本結構和資料:
<script>
var app = new Vue({
el: '#app',
data: {
arrayData: [
{
name: '小明',
age: 16
},
{
name: '漂亮阿姨',
age: 24
},
{
name: '杰倫',
age: 20
}
],
objectData: {
ming: {
name: '小明',
age: 16
},
auntie: {
name: '漂亮阿姨',
age: 24
},
jay: {
name: '杰倫',
age: 20
}
},
filterArray: [],
filterText: ''
},
methods: {
reverseArray: function(){
this.arrayData.reverse();
},
filterData: function(){
var vm = this;
vm.filterArray = vm.arrayData.filter(function(item){
return item.name.match(vm.filterText);
});
},
cantWork: function(){
//this.arrayData.length = 0;
//this.arrayData[0] = {
// name: '小強',
// age: 99
//}
Vue.set(this.arrayData, 0,{
name: '小強',
age: 99
})
}
}
});
</script>
1. 陣列與物件(key value pair)
都可以使用 v-for 來執行。但兩者取出索引指略有不同,在陣列取出索引的是 index,而物件的取出索引是 key 值,
或稱物件的屬性。
先來複習一下基本使用 v-for 的方法:
<ul>
<li v-for="(item, key) in arrayData">
{{ key }} - {{ item.name }} {{ item.age }} 歲
</li>
</ul>
這樣就會把 arrayData 內的每筆資料撈出來
2. Key
接著如果我們的資料是帶有 input 元素時,就會產生一些問題,假設我們要反轉陣列,先在 button 元素內
新增點擊會反轉陣列的 reverseArray function
<ul>
<li v-for="(item, key) in arrayData">
{{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
</li>
</ul>
<button class="btn btn-outline-primary" @click="reverseArray">反轉陣列</button>
<script>
var app = new Vue({
el: '#app',
data: {
arrayData: [
{
name: '小明',
age: 16
},
{
name: '漂亮阿姨',
age: 24
},
{
name: '杰倫',
age: 20
}
],
objectData: {
ming: {
name: '小明',
age: 16
},
auntie: {
name: '漂亮阿姨',
age: 24
},
jay: {
name: '杰倫',
age: 20
}
},
filterArray: [],
filterText: ''
},
methods: {
reverseArray: function(){
this.arrayData.reverse();
},
filterData: function(){
var vm = this;
vm.filterArray = vm.arrayData.filter(function(item){
return item.name.match(vm.filterText);
});
},
cantWork: function(){
//this.arrayData.length = 0;
//this.arrayData[0] = {
// name: '小強',
// age: 99
//}
Vue.set(this.arrayData, 0,{
name: '小強',
age: 99
})
}
}
});
</script>
這時候如果我們可以在 input 內輸入一些內容,會發現 input 內的資料並沒有辦法做反轉,僅只有 arrayData 的資料
在做反轉,這時候我們要增加 key 屬性來讓 Vue 重新做渲染。根據 Vue 的官方 文件 表示,v-for 正在更新已渲染過的
元素列表時,需要特別注意它默認用「就地複用」策略。
所謂的就地複用指的是,如果數據項的順序被改變,
Vue不會移動DOM元素來匹配數據項的順序,
而是直接重複使用原本位置上的元素。
舉例來說,下圖是資料反轉前後的對照圖,可以看到資料反轉,但輸入框不會隨數據項順序改變而一起變動:
一般來說,這樣的默認模式效能較好,且在資料展示的場景中並不會有這個困擾。但若子元素間存在相依性、
存在與使用者互動的場景、或是依賴臨時的 DOM 狀態,則不推薦使用就地複用模式。
例如:上例中可以讓使用對所要輸入的表單進行重新排序,那麼建議在 v-for 為每個節點加上 key 值,
它能方便 Vue 跟蹤每個節點,從而重用和重新排序現有元素。
那這裡就先用 item.age 來做 :key 屬性的值,這時候 input 元素也會跟著做反轉了。
<ul>
<li v-for="(item, key) in arrayData" :key="item.age">
{{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
</li>
</ul>
<button class="btn btn-outline-primary" @click="reverseArray">反轉陣列</button>
假設我們現在要在 v-for 做過濾資料,要在欄位內輸入資料的名字,並把那筆資料撈出來:
分別先把定義好的資料 filterText 和 filterArray 綁上去 input 元素和 v-for 內,
並在 input 元素內按下 Enter 鍵時,執行 filterData 這個 function
<input type="text" class="form-control" v-model="filterText" @keyup.enter="filterData">
<ul>
<li v-for="(item, key) in filterArray" :key="item.age">
{{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
</li>
</ul>
<script>
var app = new Vue({
el: '#app',
data: {
arrayData: [
{
name: '小明',
age: 16
},
{
name: '漂亮阿姨',
age: 24
},
{
name: '杰倫',
age: 20
}
],
objectData: {
ming: {
name: '小明',
age: 16
},
auntie: {
name: '漂亮阿姨',
age: 24
},
jay: {
name: '杰倫',
age: 20
}
},
filterArray: [],
filterText: ''
},
methods: {
reverseArray: function(){
this.arrayData.reverse();
},
filterData: function(){
var vm = this;
vm.filterArray = vm.arrayData.filter(function(item){
return item.name.match(vm.filterText);
});
},
cantWork: function(){
//this.arrayData.length = 0;
//this.arrayData[0] = {
// name: '小強',
// age: 99
//}
Vue.set(this.arrayData, 0,{
name: '小強',
age: 99
})
}
}
});
</script>
filterData 裡面使用 vm = this 是因為可避免內部有其他函式呼叫導致 this 運作不如預期,所以若是
有要在函式裡面使用 data 內宣告的變數,盡量用 vm = this 確保存取的變數是 Vue data 宣告的變數。
這裡值得一提的是運用了 .filter() 和 .match() 這兩個方法:
filter( )會建立一個經指定之函式運算後,由原陣列中通過該函式檢驗之元素所構成的新陣列。把傳入的函式
依次作用於每個元素,然後根據返回值是 true 還是false 決定保留還是丟棄該元素。很適合用在搜尋符合條件的資料。
match( )是找尋是否有符合的字串,所以輸入部分文字,若是有符合內容就會顯示結果。
這邊可能會有一個疑問,在一開始 input 尚未輸入文字時, filterText 是空的,所以使用 item.name == vm.filterText
一開始自然是撈不到資料。但是為什麼 item.name.mach(vm.filterText) 可以一開始 filterText 是空的時候便撈到全部的
資料?
那是因為空值時也會有回傳結果,空的陣列也會被視為真值,而 null 才會被視為假值。而 filter 內容只要回傳的是真值就
會將該資料回傳,所以空的欄位一樣會回傳全部資料。(另外,在此實際上並非「空陣列」,只有 null 才不會回傳內容)
以這個範例來說:
可以把 index 想成是符合字串的索引位置, input 是來源值,也就是 str 。
回到本章節範例,而 match( ) 完得到的結果是 return null , filter( ) 的條件 true 所以會將所有資料 return 給
filterArray 變數。
那資料是從哪裡被傳入給 filter( ) 的呢?
回到 item.name.match(vm.filterText) , vm.vm.filterText 就是自己傳入的值,當這個值為空值時,
則會輸出一個陣列如下(可以不思考內容為何):
在 filter 中,只要 return 為 true 就會回傳該筆資料。上述中有回傳一個陣列,陣列是被判斷為真值,因此就會回傳
該筆資料,所以與陣列的資料內容沒有關聯性喔!
再來,你會不會有疑問當 arrayData 替換成 filterArray 的時候,一開始 filterArray 裡面沒有資料,是等到過濾後才會
回傳結果,那畫面一開始預設的三筆資料是從哪裡來的?
原因是 match( ) 這個功能會檢索指定字串是否符合內容,是的話便回傳該字串, vm.filterText 在不輸入值的情況下
是 "" ,但因為 item.name 是字串型別所以他們天生就會帶有 "" ,而 “小明”、”漂亮阿姨”、”杰倫”這三個字串在
match( ) 判斷下是符合的,因此會將三個內容都傳到 filterArray 上, v-for 再去跑 filterArray 三筆資料都會顯示囉!
所以 filterArray return 的陣列是長這樣:
[
{
name: '小明',
age: 16
},
{
name: '漂亮阿姨',
age: 24
},
{
name: '杰倫',
age: 20
}
],
3. 純數字的迴圈
另外,我們也可以輸出純數字的迴圈:
<ul>
<!-- <li v-for="item in 輸入數字範圍"> -->
<li v-for="item in 10">
{{ item }}
</li>
</ul>
如果我們是 in 一個數字就是繞出一個數字的迴圈
4. Template 的運用
當輸出的有特定之格式,我們可以使用 <template> 書寫,搭配 v-for 來做使用,template 在最後產生 HTML 時
並不會被渲染出來,但它可以用放上 Vue 的指令。
假設我們的目的是一次輸出兩個 <tr> :
<table class="table">
<template v-for="item in arrayData">
<tr>
<td>{{ item.age }}</td>
</tr>
<tr>
<td>{{ item.name }}</td>
</tr>
</template>
</table>
5. v-for 與 v-if
在 v-for 內,有時候我們會搭配 v-if 來做使用,撈出符合判斷式的資料:
<ul>
<li v-for="(item, key) in arrayData" v-if="item.age <= 20">
{{ key }} - {{ item.name }} {{ item.age }} 歲
</li>
</ul>
上面我們就新增了一個 v-if 並只顯示出年齡小於等於 20 歲的資料
6. 不能運作的狀況
在 Vue 裡面,有些狀況會無法運作,注意!不要直接更動 array 的內容,即便透過 console.log 和 Vue 工具看資料
都是修改過的,但畫面依然顯示舊資料,因為視圖無更新。
我們一樣先把 arrayData 內的資料撈出來,並新增一個 cantWork 的 function
<button class="btn btn-outline-primary" @click="cantWork">無法運行</button>
<ul>
<li v-for="(item, key) in arrayData" :key="item.age">
{{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
</li>
</ul>
<script>
var app = new Vue({
el: '#app',
data: {
arrayData: [
{
name: '小明',
age: 16
},
{
name: '漂亮阿姨',
age: 24
},
{
name: '杰倫',
age: 20
}
],
objectData: {
ming: {
name: '小明',
age: 16
},
auntie: {
name: '漂亮阿姨',
age: 24
},
jay: {
name: '杰倫',
age: 20
}
},
filterArray: [],
filterText: ''
},
methods: {
reverseArray: function(){
this.arrayData.reverse();
},
filterData: function(){
var vm = this;
vm.filterArray = vm.arrayData.filter(function(item){
return item.name.match(vm.filterText);
});
},
cantWork: function(){
//情境一:直接修改陣列長度(在一般 js 中,表示刪除所有資料)
//this.arrayData.length = 0;
//情境二:透過索引來取代資料
//this.arrayData[0] = {
// name: '小強',
// age: 99
//}
Vue.set(this.arrayData, 0,{
name: '小強',
age: 99
})
}
}
});
</script>
第一種不能運作的情況是當我們把 arrayData 的長度改為 0 的時候,照理說資料就會被刪光,可是當我們點擊 button 元素
時,資料並沒有不見。
第二種不能運作的情況是當我們把 arrayData 內的某筆資料進行改寫時,也會無效,因為 Vue 無法探測普通的新增屬性,它必
須用於向響應式對像上添加新屬性,針對這種情況有另外一種做法,這個在官方文件中有提到,我們可以使用 Vue.set( ) 強制
改寫某筆資料,上述我也改寫了第一筆資料,以上就是無法運作的情況。
v-if 與其使用細節
在前面提到 v-if 都是搭配 v-for 來做使用,但 v-if 也可以單獨的做使用
一樣先給 Vue 的架構和資料:
<script>
var app = new Vue({
el: '#app',
data: {
isSuccess: true,
showTemplate: true,
link: 'a',
loginType: 'username'
},
methods: {
toggleLoginType: function () {
return this.loginType = this.loginType === 'username' ? 'email' : 'username'
}
}
});
</script>
1. v-if、v-else、v-else-if
接著就來基本的使用 v-if,我們有兩個元件,分別可以跟著 input 的 checkbox 的勾選,來呈現不同的元件。
當 isSuccess 的資料狀態為 true 就出現成功,為 false 就出現失敗,這時候 .alert 元素都綁著 v-if 的指令
判斷式就是 isSuccess 這個資料狀態,而 input 的 checkbox 綁著 isSuccess 隨著勾選會切換資料狀態
<div class="alert alert-success" v-if="isSuccess">成功!</div>
<div class="alert alert-danger" v-if="!isSuccess">失敗!</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="isSuccess" v-model="isSuccess">
<label class="form-check-label" for="isSuccess">啟用元素狀態</label>
</div>
在 JS 裡面 if 判斷式常常搭配 else 來做使用,所以在 Vue 裡面當然也有 v-else 這個指令,所以在上面的程式碼,我們可以把﹝失敗的元件﹞改為 v-else,如下:
<div class="alert alert-success" v-if="isSuccess">成功!</div>
<div class="alert alert-danger" v-else>失敗!</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="isSuccess" v-model="isSuccess">
<label class="form-check-label" for="isSuccess">啟用元素狀態</label>
</div>
兩種寫法都是一樣的。
當然,我們也可以搭配 <template> 來做使用,舉一種情境,假設我們的表格資料,都要隨著 checkbox 的勾選來顯示或隱藏,這樣我們勢必就要在每個 <tr> 內下 v-if 的判斷式,假設有很多 <tr> 呢?那是不是太累了?
<table class="table">
<thead>
<th>編號</th>
<th>姓名</th>
</thead>
<tr v-if="showTemplate">
<td>1</td>
<td>安妮</td>
</tr>
<tr v-if="showTemplate">
<td>2</td>
<td>小明</td>
</tr>
</table>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="showTemplate" v-model="showTemplate">
<label class="form-check-label" for="showTemplate">啟用元素狀態</label>
</div>
這時候就可以使用 <template> 了,使用之後寫法會如下,而 v-if 的指令只需要下在 <template> 內,而 <template> 是不會被輸出的
<table class="table">
<thead>
<th>編號</th>
<th>姓名</th>
</thead>
<template v-if="showTemplate">
<tr>
<td>1</td>
<td>安妮</td>
</tr>
<tr>
<td>2</td>
<td>小明</td>
</tr>
</template>
</table>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="showTemplate" v-model="showTemplate">
<label class="form-check-label" for="showTemplate">啟用元素狀態</label>
</div>
接下來,我們來做頁籤切換內容的功能,如下:
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" href="#" :class="{'active': link == 'a'}" @click.prevent="link = 'a'">標題一</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" :class="{'active': link == 'b'}" @click.prevent="link = 'b'">標題二</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" :class="{'active': link == 'c'}" @click.prevent="link = 'c'">標題三</a>
</li>
</ul>
<div class="content">
<div v-if="link == 'a'">A</div>
<div v-else-if="link == 'b'">B</div>
<div v-else-if="link == 'c'">C</div>
</div>
比較不一樣的是多了 v-else-if 這個指令,這個就等於 JS 內的 else if,而顯示跟隱藏的方式,我們是採用 link 這個字串資料內容。
2. v-if v.s v-show
根據 教學文章 說明,因為 v-if 是真實的條件渲染,在切換 v-if 區域時,Vue.js 有一個局部編譯/卸載過程,
它會確保該區域在切換過程中能合適地銷毀與重建條件塊內的事件監聽器和子組件。
且 v-if 是惰性的:如果在初始渲染時條件不成立,就什麼也不做,直到在條件第一次變為成立後才開始局部編譯
(編譯會被暫存起來)。v-if 在資料狀態改變時,是採用移除 DOM 元素的方式。
相比之下,v-show 元素則是全部會被編譯並保留,只是簡單地基於 CSS 切換,採用 display 的屬性來做元素的隱藏跟顯示,
亦即資料狀態為 true 時,display 為 block,資料狀態為 false 時,display 為 none 。
一般來說,v-if 有更高的切換消耗而 v-show 有更高的初始渲染消耗。因此,如果需要頻繁切換,則使用 v-show 較好;
如果在運行時條件不大可能改變,則使用 v-if 較好。
3. Key
Vue 會盡可能高效地渲染元素,通常會重複用已有元素而不是從頭開始渲染。但這種方式可能會造成了畫面資訊
不一致的狀況,舉例來說:
<template v-if="loginType === 'username'">
<label>Username</label>
<input class="form-control" placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input class="form-control" placeholder="Enter your email address">
</template>
因為兩個模板使用了相同的元素,input 不會被替換掉,僅僅是替換了它的 placeholder 。但是一旦你在輸入框輸入文字,
就會出現下面的情況:
你會發現點擊了切換狀態後,<label> 內容已經變換,但是剛才在 <input> 輸入的內容卻沒有消失。
為了使完整渲染,可在輸入框元素加上 :key ,一旦 :key 不同就會重新渲染。注意這邊 :key 只加在輸入框上,
因此 label 依舊會被重複使用。
<template v-if="loginType === 'username'">
<label>Username</label>
<input class="form-control" placeholder="Enter your username" :key="username">
</template>
<template v-else>
<label>Email</label>
<input class="form-control" placeholder="Enter your email address" :key="email">
</template>
Computed 與 Watch
接下來要來介紹 Vue 裡面的 computed(計算) & watch(監聽)。
Method:這是需要主動觸發,且可以多次重複觸發。Computed:是針對我們要輸入到畫面的內容,在輸出前需要做前處理。簡單來說,當資料出現
變化需要改變畫面的 function 放這裡 。
Watch:監控特定變數,當該變數產生變化時,可執行特定事件。
舉個例子,一樣先建構 Vue 的架構跟資料狀態:
<script>
var app = new Vue({
el: '#app',
data: {
arrayData: [
{
name: '小明',
age: 16
},
{
name: '漂亮阿姨',
age: 24
},
{
name: '杰倫',
age: 20
}
],
filterText: '',
trigger: false,
newDate: 0
}
});
</script>
我們一樣來過濾資料,但這次不採用點擊任何按鈕的方式,我們要在 input 欄位內輸入文字時,就可以把那筆資料撈出來:
<input type="text" class="form-control">
<ul>
<li v-for="(item, key) in arrayData" :key="item.age">
{{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
</li>
</ul>
正常這樣寫,我們就可以把每筆資料都顯示出來,但是為了要做到過濾功能,我們就可以使用 computed 功能來幫忙,我們先在
input 元素上綁定先定義好的 filterText 資料,接著把 arrayData 換成 filterArray ,不同於前面的是,這次
filterArray已經不是我們先定義好的變數了,我們要藉由 computed 來產生,完成之後會如下:
<input type="text" class="form-control" v-model="filterText">
<ul>
<li v-for="(item, key) in filterArray" :key="item.age">
{{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
</li>
</ul>
接著我們要在 Vue 裡面的 data 下方,增加一個 computed ,並把要呈現的 filterArray 寫成一個 function 接著我們會
return 我們要的值,而要 return 的值,就是符合 filterText 的文字的資料,這時候一樣可以用到 .filter( ) 和
.match( ) 如下:
var app = new Vue({
el: '#app',
data: {},
computed: {
filterArray: function(){
var vm = this;
return vm.arrayData.filter(function(item){
return item.name.match(vm.filterText);
});
}
}
});
這個時候就會發現,功能已經如我們預期的完成了。
接下來我們要使用 Computed 來呈現時間格式
首先在 Vue 裡面新增 mounted function ,當我們畫面運行完畢之後,執行 newDate ,而 Math.floor(Date.now() / 1000);
這段是 timestamp 格式。
我們將 newDate 以兩個花括弧盛裝,放入 html 裡面,畫面上會呈現一串數字,例如: 1523930859,這其實是 timestamp
格式,它可以透過 javascript 再轉換為我們平常看到的時間格式。
這時候我們用 Computed 將這個數值改成我們常看到的時間格式更為方便。
我們在 Vue 裡面再新增一個 formatTime function , function 最後的數值都是透過 return 出來的,而 timestamp 我們在
使用的時候,可以直接用 newDate 的方式將它的值轉回原本的時間格式,若時間長度不足的時候,可以直接用乘於 * 1000 或
10000 的方式把它的時間補回原本的長度,接下來可以透過 year 、 month 、 date …等方式,將原本的時間取出來,再將
時間格式拼湊出來 ${year}/${month}/${date} ${hours}:${minutes}:${seconds} 。
<div id="app">
<p>使用 Computed 來呈現時間格式。</p>
<p>{{ newDate }}</p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {},
computed: {
filterArray: function(){
var vm = this;
return vm.arrayData.filter(function(item){
return item.name.match(vm.filterText);
});
},
formatTime: function () {
console.log(this.newDate)
var dates = new Date(this.newDate * 1000);
var year = dates.getFullYear();
var month = dates.getMonth() + 1;
var date = dates.getDate() + 1;
var hours = dates.getHours();
var minutes = dates.getMinutes();
var seconds = dates.getSeconds();
return `${year}/${month}/${date} ${hours}:${minutes}:${seconds}`
}
},
mounted: function () {
this.newDate = Math.floor(Date.now() / 1000);
}
});
</script>
最後我們將剛才 html 的 ,替換成 ,畫面上就會看到目前的時間,
例如:2018/4/18 10:10:46
<div id="app">
<p>使用 Computed 來呈現時間格式。</p>
<p>{{ formatTime }}</p>
</div>
接下來是 watch,watch 的運用上,是當我們監聽某個資料,當有變動時就會執行 function,
一樣來個例子:
<div class="box" :class="{'rotate': trigger }"></div>
<button class="btn btn-outline-primary" @click="trigger = 'true'">Counter</button>
前面我們已經有先定義好 trigger 這個變數了,當我們點擊按鈕時,trigger 會變成 true 且 .box 會增加 .rotate 這個
class ,而我們要監聽的就是 trigger 這個變數資料,我希望 3 秒之後,他的資料狀態回到 false 狀態。
這時候在 Vue 裡面除了先前的 data 和 computed 之外,也需要增加一個 watch 了,而我們要監聽的就是 trigger ,
所以寫一個當 trigger 資料變動時要執行的 function 。
var app = new Vue({
el: '#app',
data: {},
computed: {},
watch: {
trigger: function(){
var vm = this;
setTimeout(function(){
vm.trigger = false
},3000);
}
}
});
這時候當我們第一次點擊按鈕時, trigger 的資料狀態會變為 true 而 3 秒之後就會變回去 false 了。
表單細節操作
在先前已經有講過表單的運用,這邊來補充表單其餘的使用方式,一樣先定義好 Vue 資料結構:
<script>
var app = new Vue({
el: '#app',
data: {
singleRadio: '',
selected: '',
selectData: ['小美', '可愛小妞', '漂亮阿姨'],
selected2: '',
multiSelected: [],
sex: '男生',
// 修飾符
lazyMsg: '',
age: '',
trimMsg: ''
},
});
</script>
- 在寫
selected時候我們可以透過v-model,以及option裡面的value將值帶進去
<div id="app">
<h4>Select</h4>
<select name="" id="" class="form-control" v-model="selected">
<option disabled value="">請選擇</option>
<option value="小美">小美</option>
<option value="可愛小妞">可愛小妞</option>
<option value="漂亮阿姨">漂亮阿姨</option>
</select>
<p>小明喜歡的女生是 {{ selected }}。</p>
</div>
- 我們怎麼透過
v-for來製作selected裡面的資料,我們可以直接使用option,在option裡面使用v-for再將值
"item in selectData" 帶入,那 value 的部份我們可以直接用 item 去繪製,但是這邊需要注意 value 是動態的屬性,
所以在 value 前面要加上冒號 : ,最後將 插入<option> 。
<div id="app">
<select name="" id="" class="form-control" v-model="selected2">
<option disabled value="">請選擇</option>
<option :value="item" v-for="item in selectData">{{ item }}</option>
</select>
<p>小明喜歡的女生是 {{ selected2 }}。</p>
</div>
結果會跟上方一樣。
- 除了上方的單選之外還可以做多選,我們可以透過
HTML特性將資料改為多選。只要在select標籤內增加
multiple 屬性
<div id="app">
<h4 class="mt-3">多選</h4>
<select name="" id="" class="form-control" multiple v-model="multiSelected">
<option value="小美">小美</option>
<option value="可愛小妞">可愛小妞</option>
<option value="漂亮阿姨">漂亮阿姨</option>
</select>
<p>小明喜歡的女生是 <span v-for="item in multiSelected">{{ item }} </span>。</p>
</div>
加上這個屬係性之後畫面上就會長這樣:
補充:可透過
shift或cmd來多選,windows則是shift或ctrl
- 接下來介紹複選框,首先畫面上的值目前點選後只能顯示
true或false
但是我們希望它呈現的時候是顯示男生或女生話,要怎麼製作呢?
我們可以在 input 標籤裡面加上 true-value="男生" ,意思是當它在 true 的狀態時後就會顯示男生,相反的,
加上 false-value="女生" ,當它在 false 的狀態時後就會顯示女生。
<div id="app">
<h4 class="mt-3">複選框</h4>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="sex" v-model="sex" true-value="男生" false-value="女生">
<label class="form-check-label" for="sex">{{ sex }}</label>
</div>
</div>
新增後的畫面長這樣:
- 接下來介紹修飾符,在
v-model裡面有一些修飾符可以使用,主要有3個。
(1) lazy
在畫面上可以看到我們在 input 裡面輸入值,它會直接反應在上方。
現在改變值的方式接近於 keyup 但是我們要改成 onchange 的方式,就可以將 .lazy 加在 v-model 後方,
即 v-model.lazy="lazyMsg" ,加入後我們可以看到畫面上在 input 裡面輸入值,上方是不會有反應的,
直到我們點即外框才會顯示。
<div id="app">
<h4 class="mt-3">修飾符</h4>
{{ lazyMsg }}
<input type="text" class="form-control" v-model.lazy="lazyMsg">
</div>
這個方式就是類似於我們用 onchange 方式在監控 input 。
(2) number
<div id="app">
<pre>{{ typeof(age) }}</pre>
<input type="number" class="form-control" v-model="age">
</div>
可以看到目前我們將 input 的 type 設定為 number ,但實際上輸出的值不一定是純數值的屬性,我們可以在 <pre>
輸入 typeof(age) 看一下目前 age 的格式是什麼?結果是 string 。
我們可以在 v-model 後方加上 .number ,透過 number 這個修飾符將所輸入的數值轉為純數值的格式,
最後將 一起放入 <pre> ,看一下畫面。
<div id="app">
<pre>{{ typeof(age) }} {{ age }}</pre>
<input type="number" class="form-control" v-model.number="age">
</div>
(3) trim
我們輸入值的時候有時文字的前後會有空白鍵。
這時候我們可以使用修飾符 trim ,加入這個修飾符後它會自動將多餘的空白去除掉。
<div id="app">
{{ trimMsg }}緊黏的文字
<input type="text" class="form-control" v-model.trim="trimMsg">
</div>
v-on 的頁面操作細節
這邊要介紹在動元素上操作 v-on 有哪些額外的技巧。
1. 切換 box 樣式
前面有介紹過在一個動元素上加上 v-on:click ,後面加上方法的名稱 changeRotate ,這樣就可以對一個動元素做操作。
<style>
.box {
display: block;
transition: all .5s;
}
.box.rotate {
transform: rotate(45deg)
}
</style>
<div id="app">
<p>請切換下方 box 的 className</p>
<div class="box" :class="{'rotate': isRotate }"></div>
<hr>
<button class="btn btn-outline-primary" v-on:click="changeRotate">切換 box 樣式</button>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
arrayData: [
{
name: '小明',
age: 16,
cash: 500
},
{
name: '漂亮阿姨',
age: 24,
cash: 1000
},
{
name: '杰倫',
age: 20,
cash: 5000
}
],
isRotate: false,
text: ''
},
methods: {
changeRotate: function() {
this.isRotate = !this.isRotate;
},
storeMoney: function(item) {
item.cash = item.cash + 500;
},
trigger: function(name) {
console.log(name, '此事件被觸發了')
}
}
});
</script>
2. 帶入參數
可以看到 v-for 裡面的 arrayData 是 Vue data 已定義好的物件,裡面包含名稱 及多少錢
@click="storeMoney(item)" ,而這邊的參數 item 會傳到 storeMoney function 裡面,所以每次點擊按鈕就會加 500 元。
<div id="app">
<h4>帶入參數</h4>
<ul>
<li v-for="item in arrayData" class="my-2">
{{ item.name }} 有 {{ item.cash }} 元
<button class="btn btn-sm btn-outline-primary" @click="storeMoney(item)">儲值</button>
</li>
</ul>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
arrayData: [
{
name: '小明',
age: 16,
cash: 500
},
{
name: '漂亮阿姨',
age: 24,
cash: 1000
},
{
name: '杰倫',
age: 20,
cash: 5000
}
],
isRotate: false,
text: ''
},
methods: {
changeRotate: function() {
this.isRotate = !this.isRotate;
},
storeMoney: function(item) {
item.cash = item.cash + 500;
},
trigger: function(name) {
console.log(name, '此事件被觸發了')
}
}
});
</script>
我們看一下結果:
這邊點擊儲值按鈕是不會互相影響到其他物件的。
3. 介紹動元素上面的事件修飾符
- .stop - 調用 event.stopPropagation( ) 冒泡事件
假設我點擊了 box ,它會傳送 box 的訊息。點擊了 div ,它會傳送 div 的訊息。
我們打開開發者工具來檢視,當我點擊了 box ,它跳出了兩個訊息(如下圖),可以看到 box 在裡面 div 在外面。
<h6>將此範例加上 stopPropagation</h6>
<div class="p-3 bg-primary" @click="trigger('div')">
<span class="box" @click="trigger('box')"></span>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
arrayData: [
{
name: '小明',
age: 16,
cash: 500
},
{
name: '漂亮阿姨',
age: 24,
cash: 1000
},
{
name: '杰倫',
age: 20,
cash: 5000
}
],
isRotate: false,
text: ''
},
methods: {
changeRotate: function() {
this.isRotate = !this.isRotate;
},
storeMoney: function(item) {
item.cash = item.cash + 500;
},
trigger: function(name) {
console.log(name, '此事件被觸發了')
}
}
});
</script>
若點擊外面的話當然只有顯示 div 會被觸發,因為我們在觸發動元素的時候是由內而外的傳播,
雖然我們點擊了裡面的 box 但是也會觸發到外面的 div 。
要避免這個狀況發生,我們可以使用 .stop 加在 box 及 div 的 click 事件上,
這樣在傳播訊息的時候就不會向外地不斷觸發。
<h6>將此範例加上 stopPropagation</h6>
<div class="p-3 bg-primary" @click.stop="trigger('div')">
<span class="box" @click.stop="trigger('box')"></span>
</div>
- .capture - 添加事件偵聽器時使用 capture 模式
我們先點擊「按我」這個按鈕,打開開發者工具,你可以發現它觸發了三個事件分別是由內而外的 button 、 box 、 div
<h6 class="mt-3">事件偵聽器時使用 capture 模式</h6>
<div class="p-3 bg-primary" @click="trigger('div')">
<span class="box d-flex align-items-center justify-content-center" @click="trigger('box')">
<button class="btn btn-outline-secondary" @click="trigger('button')">按我</button>
</span>
</div>
現在我們在程式碼 @click 加上 .capture 後,再次點擊「按我」這個按鈕,你會發現事件觸發的順序相反了,變成 div
、 box 、 button ,這個 capture 其實有點像 Propagation 相反的版本, capture 是由外而內的傳播。
<h6 class="mt-3">事件偵聽器時使用 capture 模式</h6>
<div class="p-3 bg-primary" @click.capture="trigger('div')">
<span class="box d-flex align-items-center justify-content-center" @click.capture="trigger('box')">
<button class="btn btn-outline-secondary" @click.capture="trigger('button')">按我</button>
</span>
</div>
- .self - 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。
.self 就是指會觸發它自己這個元素,我們在 @click 上都加上 .self 。
<h6 class="mt-3">事件偵聽器時使用 self 模式</h6>
<div class="p-3 bg-primary" @click.self="trigger('div')">
<span class="box d-flex align-items-center justify-content-center" @click.self="trigger('box')">
<button class="btn btn-outline-secondary" @click.self="trigger('button')">按我</button>
</span>
</div>
可以看到當我們點擊元素時它就不會觸發其他的元素。
- .once - 只觸發一次回調。
我們在 @click 後方加上 .once 。
<h6 class="mt-3">事件偵聽器只觸發一次</h6>
<div class="p-3 bg-primary" @click.once="trigger('div')">
<span class="box d-flex align-items-center justify-content-center" @click.once="trigger('box')">
<button class="btn btn-outline-secondary" @click.once="trigger('button')">按我</button>
</span>
</div>
點擊後可以看到事件被觸發,當再一次點擊事件就不會再觸發了,因為 .once 只會被觸發一次。
4. 按鍵修飾符
按鍵修飾符就是我們在按下鍵盤的按鈕時,它會觸發特定的事件。
- .keyCode
只當事件是從特定鍵觸發時才觸發回調。例如:@keyup.13,13 為 enter 的 keyCode 。
鍵盤上的每個按鈕都會有編號,而這個編號可稱為
keyCode,想知道按鍵代表的keyCode是多少可以查閱 文件
<h6 class="mt-3">keyCode</h6>
<input type="text" class="form-control" v-model="text" @keyup.13="trigger(13)">
- 別名修飾
別名修飾,例如:.enter, .tab, .delete, .esc, .space, .up, .down, .left, .right 。
我們試著在 @keyup 後面加上 .space ,在畫面上 input 欄位輸入內容後,按下鍵盤 enter 或其他按鍵都不會觸發事件,
只有按下 .space 事件才會被觸發。
<h6 class="mt-3">別名修飾</h6>
<input type="text" class="form-control" v-model="text" @keyup.space="trigger('space')">
- 相應按鍵時才觸發的監聽器
.ctrl 、 .alt 、 .shift 、 .meta。 例如:@keyup.shift.enter,必須同時按下 shift 及 enter 才會觸發。
<h6 class="mt-3">相應按鍵時才觸發的監聽器</h6>
<input type="text" class="form-control" v-model="text" @keyup.shift.enter="trigger('shift + Enter')">
5. 滑鼠修飾符
我們可以針對滑鼠的特定按鍵觸發事件。
方法有這些:
.left- 只當點擊鼠標左鍵時觸發。.right- 只當點擊鼠標右鍵時觸發。.middle- 只當點擊鼠標中鍵時觸發。
舉例,在 @click 後方加上 .right ,在畫面上用滑鼠點擊右鍵後就會觸發事件了。
<h6 class="mt-3">滑鼠修飾符</h6>
<div class="p-3 bg-primary">
<span class="box" @click.right="trigger('Right button')">
</span>
</div>