VueJS

整理 Vue.js 筆記

v-model 資料綁定

基本上 Vue 的資料綁定,就是直接用 v-model 加上 data 裡的變數名稱,即可完成綁定。

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="app">
<!-- v-model 綁定 -->
<input type="text" class="form-control" v-model="text">
<!-- 即時呈現 -->
{{ text }}
</div>

<script>
var app = new Vue({
el: '#app',
data: {
text: '',
},
});
</script>

不過唯一要注意的事情,就是綁定的是什麼元素,如:inputCheckboxradioselect..等,
我們需要依照不同的狀況綁定不同的資料格式,如下

1
2
3
4
5
6
7
8
9
10
11
var app = new Vue({
el: '#app',
data: {
text: '', // input標籤
textarea: '', // textarea標籤
checkbox: false, // 單選的checkbox
checkboxArray: [], // 多選的checkbox
singleRadio: '', // radio標籤
selected: '' // select標籤
}
});

由上例來看,我們需特別注意的是 HTML-checkbox 標籤

  • 單選的 checkbox,資料儲放的格式需為boolean型態。
  • 多選的 checkbox,資料儲放的格式需為array型態。

官方:表單控件綁定


表單元素绑定 value

有時我們使用 radiocheckboxselected時,
要達成呈現文字儲存的資料要有所不一樣的話,
我們就可以透過value來設定,
另外若需要綁定 value 到 Vue 實例的一個動態屬性上,這時可以用v-bind實現。

範例

1
2
3
4
5
6
7
8
9
10
<!-- 當選中時,`picked` 為字符串"a" -->
< input type = "radio" v-model = "picked" value = "a" >

<!-- `toggle` 為true 或false -->
< input type = "checkbox" v-model = "toggle" >

<!-- 當選中時,`selected` 為字符串"abc" -->
< select v-model = "selected" >
< option value = "abc" > ABC </ option >
</ select >

select 使用 v-for 渲染

主要重點在於賦予option的 value 時,記得要加:符號,如下例

1
2
3
4
<select name="" id="" v-model="selected2">
<option disabled value="">請選擇</option>
<option :value="item" v-for="item in selectData">{{ item }}</option>
</select>

官方:值绑定

v-model 修飾符

1
2
3
4
5
6
7
8
<!-- lazy 就像 onchange 一樣,離開欄位才會觸發 -->
<input type="text" v-model.lazy="lazyMsg">

<!-- 將值轉型為 數字型態 -->
<input type="number" v-model.number="age">

<!-- 去頭去尾多餘的空白 -->
<input type="text" v-model.trim="trimMsg">

官方:修飾符

複選框

主要是在 select的標籤上,新增multiple這個屬性即可。

1
2
3
4
5
<select name="" id="" multiple v-model="multiSelected">
<option value="Kanboo">Kanboo</option>
<option value="Lucas">Lucas</option>
<option value="Mary">Mary</option>
</select>

連結:JSBin


呈現文字的方式

簡單來說有下列幾種

  • 直接輸出:{ {message;}}
  • 綁定在HTML Tag標籤
    • 純文字: v-text
    • HTML: v-html

範例

1
2
3
4
5
6
7
8
9
10
<div id="app">
<!-- 直接輸出 -->
{{ message }}

<!-- v-text -->
<div v-text="message"></div>

<!-- v-html -->
<div v-html="message"></div>
</di>

v-bind 標籤屬性綁定

經過上面的解說與示範,
我們如何要將資料綁定在 HTML Tag標籤 HTML Tag標籤 HTML Tag標籤上的話,
可能就很直接的使用下列的方式

1
<img src="{{ imgSrc }}" alt="">

在 src 的地方,直接使用 { { imgSrc }} 就以為可以自動綁定好了。

錯誤範例

錯誤範例
1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<!-- 錯誤使用方式,須使用 v-bind -->
<img src="{{ imgSrc }}" alt="">
</div>

<script>
var app = new Vue({
el: '#app',
data: {
imgSrc: 'https://images.unsplash.com/photo-1479568933336-ea01829af8de?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=d9926ef56492b20aea8508ed32ec6030&auto=format&fit=crop&w=2250&q=80'
}
})
</script>

But…人生總是不如意,上例案例並無法將圖片網址正確的綁上 img 標籤 上,
正確的使用方式,應該要使用 v-bind 這功能,如下例

<img v-bind:src="imgSrc" alt="">

所以要在 img 標籤 針對 Attributes 做綁定的話,就要加上 v-bind

正確範例

正確範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app">
<!-- 針對 Attributes 做綁定的話,就要加上 v-bind -->
<img v-bind:src="imgSrc" v-bind:class="className" alt="">
</div>

<script>
var app = new Vue({
el: '#app',
data: {
imgSrc: 'https://images.unsplash.com/photo-1479568933336-ea01829af8de?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=d9926ef56492b20aea8508ed32ec6030&auto=format&fit=crop&w=2250&q=80',
className: 'img-fluid'
}
})
</script>

圖片連結

當我們要讀取本地端的圖片時,如果直接輸入路徑時,會造成它無法正常顯示,
主要是因為 vue-loader 的編譯規則 所導致,我們可以改寫用require方式,來取得本地端圖片的位置。

1
2
3
4
5
<!-- 失效 -->
<img :src="@/assets/img_bg.png">

<!-- 正常 -->
<img :src="require('@/assets/img_bg.png')">

How to bind img src to data in Vue > vue tempalte 中的 img 标签无法打包出来 > vue 踩坑系列——backgroundImage 路径问题


有無冒號的區別

有冒號

有冒號為 v-bind 的縮寫,所以有冒號時,這時資料是綁定一個「變數」,
所以此時 hello 就代表綁定 Vue Data 裡的 hello 變數。

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 有冒號 -->
<my-component :message="hello"></my-component>


<script>
var vm = new Vue({
el: '#app',
data: {
hello: 'Hi',
}
});
</script>

有時不一定是變數,直接寫運算式也行

1
2
3
// 有冒號
:message="1==1"
:message="1==1?'abc':'xyz'"

無冒號

無冒號代表無綁定,所以此時 message 變數的值,就是一個 hello 的字串。

1
2
<!-- 無冒號 -->
<my-component message="hello"></my-component>

v-for 重覆渲染

在呈現多筆資料時,可使用 v-for 功能,來實現重覆渲染畫面,

一般使用時,可使用 v-for="item in list"

但若有需要資料的索引值的話,可改寫為 v-for="(item, index) in list"

這樣就可以利用 index 取得目前的索引值

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div id="app">
<ul>
<li v-for="(item, index) in list">
{{ index + 1 }} - {{ item.name }},年齡:{{ item.age }}
</li>
</ul>
</div>

<script>
var app = new Vue({
el: '#app',
data: {
list: [{
name: '小明',
age: 16
},
{
name: '媽媽',
age: 38,
},
{
name: '漂亮阿姨',
age: 24
}
]
}
})
</script>

v-for :key 的用途

由於 Vue.js 在效能考量,在預設的狀況下,Vue.js 會儘量重覆使用已渲染好的元素。
若不設定 key 值,不會重新渲染元素,只會 部份更新 部份更新 部份更新

無 key 範例

在 HTML 的綁定 li 的部份,後面並無加 :key 的設定,
然後我們在 li 裡的 input 欄位裡,分別打上 1111、2222、3333 的值後,
這時我們點擊按鈕(反轉陣列)時,你會看到資料有二種變化的情況,

  1. { { index }} - { { item.id }} { { item.name }} 的值,有正常的反轉變化。
  2. input欄位,卻沒有跟著變化。
無 key 範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div id="app">
<ul>
<!-- 這裡無設定 :key -->
<li v-for="(item, index) in list">
{{ item.id }} {{ item.name }}

<input type="text" placeholder="自己手打加上數字">
</li>
</ul>

<button @click="reverseArray" >反轉陣列</button>
</div>

<script>
new Vue({
el: '#app',
data: {
list: [
{ id: '01', name: '甲甲甲' },
{ id: '02', name: '乙乙乙' },
{ id: '03', name: '丙丙丙' }
]
},
methods: {
reverseArray: function () {
this.list.reverse();
console.log(this.list)
},
}
});
</script>

無 Key 範例:JSBin

綁定 key 範例

下面範例為有使用 :key,可以在範例看到資料變化時,整個li 裡面的元素,也會跟著一起變化。

綁定 key 範例
1
2
3
4
5
6
7
8
9
10
11
12
<div id="app">
<ul>
<!-- 綁定 Key ,Key須是 唯一值 uniqueKey -->
<li v-for="(item, index) in list" :key="item.id">
{{ item.id }} {{ item.name }}

<input type="text" placeholder="自己手打加上數字">
</li>
</ul>

<button @click="reverseArray" >反轉陣列</button>
</div>

綁定 Key 範例:JSBin

使用建議:
1. 有使用 v-for 的話,建議都要配合 key
2. key最好是都是 唯一值 uniqueKey
3. key的值可以自訂,只要不一樣即可。

v-if 判斷式

由上例延伸,如果想要在某種條件下,不顯示資料的話,

我們就可以加上 v-if 來加上判斷式,

範例

條件為 年齡小於 25 歲不顯示

1
2
3
4
5
<ul>
<li v-for="(item, index) in list" v-if="item.age < 25">
{{ index + 1 }} - {{ item.name }},年齡:{{ item.age }}
</li>
</ul>
補充:
不過良好的撰寫習慣的話,盡量避免 v-forv-if一起使用,
主要原因是會 耗效能,可以利用 computed 計算,
先過濾掉不要的的物件後,再渲染出結果。

避免 v-if 與 v-for 一起使用

最主要的原因是「效能」的考量。

情境說明:

假設我有 100 個 User(90 男、10 女),此時我只想顯示 10 女 的資料就好。

1
2
3
4
5
<ul>
<li v-for="u in Users" v-if="u.sex === 'female'" :key="u._id">
{{ u.name }} - {{ u.isActive }}
</li>
</ul>

不過上例的程式的執行,會跑 100 次 forloop 及判斷,
所以當有更新資料時,又會重新跑 100 次,這樣效能上就顯得不優。

這時我們就可以使用 computed 先將資料過濾好後,
再丟給 v-for 去渲染畫面,這樣就可以優化效能的部份。


v-if 與 v-show

  • v-if:若為flase時,DOM 元素是不會被渲染出來,會整個消失的。
  • v-show:若為flase時,則是使用 disply:none 將元素隱藏。

v-on 事件綁定

以往我們要將某個 DOM 元素綁上事件的話,會使用 EventTarget.addEventListener() 來賦予功能,
而在 Vue 要綁上事件監聽的話,實現步驟如下

  1. 在 DOM 元素,宣告綁定事件
  2. 在 Vue 的 methods 裡,建立函式

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
<!-- 1. 在DOM元素,宣告 綁定事件 -->
<button v-on:click="reverseText">反轉字串</button>

</div>

<script>
var app = new Vue({
el: '#app',
data: {
text: '',
},
methods: {
// 2. 在Vue的methods裡,建立 函式
reverseText: function () {
console.log('Click me');
}
}
});
</script>

HTML 各種事件屬性:HTML Event Attributes


v-on 進階指令(修飾符)

下列介紹一些 修飾符 的使用,可協助縮短我們的程式碼。

事件修飾符

最常見的例子,就是 event.preventDefault()event.stopPropagation()

有時我們在監聽事件時,會需要取消一些 HTML 標籤 預設的行為或是停止事件冒泡

一般情況下,我們會像下列範例這樣撰寫

取消預設行為
1
2
3
4
reverseText(event) {
event.preventDefault(); // 取消預設行為
this.newText = this.text.split('').reverse().join('');
}

不過在 Vue 裡,有提供我們另外的寫法,如下

說明

事件修飾符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--阻止單擊事件繼續傳播-->
<a v-on:click.stop="doThis"></a>

<!--提交事件不再重載頁面-->
<form v-on:submit.prevent="onSubmit"></form>

<!--修飾符可以串聯-->
<a v-on:click.stop.prevent="doThat"></a>

<!--只有修飾符-->
<form v-on:submit.prevent></form>

<!--添加事件監聽器時使用事件捕獲模式-->
<!--即元素自身觸發的事件先在此處處理,然後才交由內部元素進行處理-->
<div v-on:click.capture="doThis">...</div>

<!--只當在event.target是當前元素自身時觸發處理函數-->
<!--即事件不是從內部元素觸發的-->
<div v-on:click.self="doThat">...</div>

官方說明:事件修飾符

範例

  • stop - 調用 event.stopPropagation(),停止事件冒泡 (Event Bubbling)行為。

    1
    2
    3
    4
    5
    <h6>將此範例加上 stopPropagation (防止向外尋找)</h6>
    <div @click="trigger('div')">
    <!-- 新增 event.stopPropagation() -->
    <span @click.stop="trigger('box')"></span>
    </div>

    stop 範例:JSBin

  • capture - 將監聽事件模式,更改為使用 事件捕獲 (Event Capturing) 模式,。

    1
    2
    3
    4
    5
    6
    <h6 >事件偵聽器時使用 capture 模式 (事件改為由外而內)</h6>
    <div @click.capture="trigger('div')">
    <span @click.capture="trigger('box')">
    <button @click.capture="trigger('button')">按我</button>
    </span>
    </div>

    capture 範例:JSBin

  • self - 只會觸發自己綁定的 DOM 元素,不會有Event CapturingEvent Bubbling,這樣就不用再新增event.stopPropagation()

    1
    2
    3
    4
    5
    6
    <h6>事件偵聽器時使用 self 模式 (只會觸發自己範圍內的)</h6>
    <div @click.self="trigger('div')">
    <span @click.self="trigger('box')">
    <button @click.self="trigger('button')">按我</button>
    </span>
    </div>

    self 範例:JSBin

  • once - 只觸發一次回調。

    1
    2
    3
    4
    5
    6
    <h6>事件偵聽器只觸發一次</h6>
    <div @click.once="trigger('div')">
    <span @click.once="trigger('box')">
    <button @click.once="trigger('button')">按我</button>
    </span>
    </div>

    once 範例:JSBin

按鍵修飾符

以往我們在監聽鍵盤的事件時,都是使用 event.keyCode 進行判斷,如下例

1
2
3
4
5
6
function runScript(e) {
// 判斷是否有按 Enter 鍵
if (e.keyCode === 13) {
// your code
}
}

一樣 Vue 也有提供我們另外的寫法,如下

說明

1
2
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">

另外也有提供常見的按鍵別名,如:enteresctab…等。

1
2
<!-- 同上 -->
<input v-on:keyup.enter="submit">

官方說明:按键修飾符

範例

  • {keyCode | keyAlias} - 只當事件是從特定鍵觸發時才觸發回調。

    1
    <input type="text" @keyup.13="trigger('按下Enter鍵')">
  • 別名修飾 - .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right

    1
    <input type="text" @keyup.enter="trigger('按下Enter鍵')">
  • 特殊按鍵,可同時加二個修飾符,達到特殊動作才觸發事件 - .ctrl, .alt, .shift, .meta

    1
    <input type="text" @keyup.shift.enter="trigger('shift + Enter')">

滑鼠修飾符

  • .left - (2.2.0) 只當點擊鼠標左鍵時觸發。
  • .right - (2.2.0) 只當點擊鼠標右鍵時觸發。
  • .middle - (2.2.0) 只當點擊鼠標中鍵時觸發。
1
<span class="box" @click.middle="trigger('點擊鼠標-中鍵')"></span>

v-on & v-bind 縮寫

身為專業的懶人的話,能少寫一個字就絕對不多打一個字,
下列分別介紹 v-onv-bind 的縮寫。

v-on

原先的寫法 v-on: 改為 @ 代表,參考下例。

1
2
3
4
5
// 原始寫法
<a href="#" v-on:click.prevent="reverseText">反轉字串</a>

// 縮寫
<a href="#" @click.prevent="reverseText">反轉字串</a>

v-bind

原先的寫法 v-bind: 只留下 : 代表即可,參考下例。

1
2
3
4
5
// 原始寫法
<img v-bind:src="imgSrc" >

// 縮寫
<img :src="imgSrc" >

v-class 動態切換 ClassName

有時我們會針對 DOM 元素,動態的新增移除 CSS 屬性,而在 Vue 裡,我們實現的方式,

除了使用 v-bind: 綁定資料外,也需要在後面新增條件式的判斷

這樣我們才能達到動態切換 ClassName,如下例所示

使用方式

1
2
<!-- 額外新增判斷式 -->
<div class="box" v-bind:class="{ '要加入的 ClassName' : 判斷式 }"></div>

物件寫法

  • 方法 1:直接寫
    直接使用 { '屬性名稱': 判斷式 } 寫入,若是有多個的話,可用「逗號」隔開。

  • 方法 2:物件
    有時超過二個以上的話,會大大降低可讀性, 所以將多個的 '屬性名稱': 判斷式 寫在 Vue 的 Data 裡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- 方法1:直接寫 -->
<div class="box"
:class="{ 'rotate': isTransform, 'bg-orange': boxColor }">
Style
</div>

<!-- 方法2:物件 -->
<div class="box" :class="objectClass">
Object
</div>


<script>
new Vue({
el: '#app',
data: {
isTransform: true,
boxColor: true,
// 物件
objectClass: {
'rotate': true,
'bg-orange': true,
},
},
});
</script>

程式範例:JSBin

更改 CSS 物件之判斷式

若是 CSS 採用物件寫法的話,不過當要更改判斷式的值時,要稍微注意一下取值的寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- 方法2:物件 -->
<div class="box" :class="objectClass">
Object
</div>

<!-- 取物件裡,單個屬性 -->
<button v-on:click="objectClass.rotate = !objectClass.rotate">
rotate
</button>

<br><br>

<!-- 注意,若有「-」字號,要用「中括號」方式寫法 -->
<input type="checkbox" v-model="objectClass['bg-orange']">
bg-orange
</input>

<script>
new Vue({
el: '#app',
data: {
objectClass: {
'rotate': false,
'bg-orange': false,
},
},
});
</script>

程式範例:JSBin

陣列寫法

直接利用陣列的方式,將ClassName寫進Array裡,記得要是「字串」。

1
2
3
4
<!-- 陣列 -->
<div class="box" :class="['rotate', 'bg-orange']">
Array
</div>

程式範例:JSBin

動態更新 CSS 陣列寫法

可透過 checkbox 的特性,可動態新增移除ClassName是否寫進 Array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- 陣列 -->
<div class="box" :class="arrayClass">
Array
</div>

<!--
1. 先綁定 v-model 及 設定 value 的值。
2. 籍由 checkbox 的特性,若有勾選時,才會是true,
此時才會存在在 arrayClass 裡,
若無勾選的話,就不會存在 arrayClass 裡。
3. 可打開 Vue工具 觀察 arrayClass 的變化。
-->
<input type="checkbox" v-model="arrayClass" value="rotate">
rotate
</input>
<input type="checkbox" v-model="arrayClass" value="bg-orange">
bg-orange
</input>

<script>
new Vue({
el: '#app',
data: {
arrayClass:[],
},
});
</script>

程式範例:JSBin

綁定行內樣式

:style="{ 樣式屬性 : '樣式的值' }

可以直接利用 :style 的寫法,將 CSS 屬性寫在 HTML 上面,

不過有一點要 注意 注意注意

若樣式屬性有「-」字號的話,改寫為「駝峰式命名」,將「-」去除而接著後面第一個字母改大寫

  • 駝峰式命名:

    • box-shadow => boxShadow ( S 變大寫)

    • margin-top => marginTop ( T 變大寫)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- 直接寫入HTML裡 -->
<div class="box" :style="{backgroundColor: 'orange'}"></div>
<div class="box" :style="{backgroundImage: 'url(' + img + ')' }"></div>

<!-- 取用Vue Data的值 -->
<div class="box" :style="styleObject1"></div>

<!-- 陣列方式,裡面插入多個物件 -->
<div class="box" :style="[{backgroundColor: 'orange'}, {borderWidth: '3px'}]"></div>
<div class="box" :style="[styleObject1, styleObject2]"></div>

<script>
new Vue({
el: '#app',
data: {
img: 'https://fakeimg.pl/100x100/?text=Hello',
styleObject1: {
backgroundColor: 'red',
borderWidth: '10px'
},
styleObject2: {
boxShadow: '3px 3px 5px rgba(0, 0, 0, 0.16)'
}
},
});
</script>

程式範例:JSBin


Computed 跟 method 差異點

因為這二者做的事情都好像可以完成,所以有時會不知該選ComputedMethods

比較表

method computed
使用方式 可傳參數 類變數
執行方式 每次呼叫,每次執行 快取
適用選擇 功能面、動作操作 資料格式、內容處理

使用方式 說明

二者的呼叫方式不太一樣

  • method

    method呼叫方式
    1
    2
    3
    4
    // 方式1
    this.fun();
    // 方式2
    this.fun('參數1', '參數2');
  • computed

    computed 屬性默認只設置 getter 函數,不過在需要時,還可以提供 setter 函數,如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // ...
    computed: {
    fullName: {
    // getter 函数
    get: function () {
    return this.firstName + ' ' + this.lastName
    },
    // setter 函数
    set: function (newValue) {
    var names = newValue.split(' ')
    this.firstName = names[0]
    this.lastName = names[names.length - 1]
    }
    }
    }
    // ...

    所以 computed 的呼叫方式,就是使用 gettersetter 二種用法,
    而這樣的用法,就很像我們一般在使用JS 的變數一樣,不是取值,就是覆寫值

    1
    2
    3
    4
    // getter
    getFullName = this.fullName;
    // setter
    this.fullName = '改變的值';

執行方式 說明

  • method

    每次呼叫,每次執行,所以若呼叫 10 次,就會執行 10 次。

  • computed

    與 method 不同的點,若是當資料沒有變動時,就不會重新計算,
    所以就算呼叫 10 次,資料若沒變的話,就直接回傳 cache 給你。

如何選擇

在選擇二者時,請優先考慮 computed,若 computed 可以使用且滿足你的需求,就用 computed 來執行,
另外下列簡易區分方式

  • computed

    若是性質比較偏向 data 的 資料格式內容處理 的話,可選用 computed

  • method

    可以想成我們在用 JS 的addeventlistener,通常在某些情況下,我們會呼叫函式,幫我們處理相關事宜,如:click、mouseover…等。


Computed 跟 method 觸發時機

這裡我們從 觸發時機 來比較二者差異,這樣下次要選擇時,就可以選擇較適當作法。

  • Computed:其相依data 改變時,computed 才會重新計算。

  • Methods:只要 data 有改變時,不管 有無相依 ,都會重新計算。

結論

  • 需要每次更新,就用 Methods
  • 在意效能,就用 Computed

範例

可以點擊範例裡的count按鈕,然後看 console 顯示的訊息,
你就會知道ComputedMethods 觸發時機有什麼不一樣了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div id="app">
<button @click="plus">count:{{ count }}</button>

<p>now (computed):{{ now }}</p>
<p>getNow (method):{{ getNow() }}</p>
</div>

<script>
var vm = new Vue({
el: '#app',
data: {
count: 0
},
methods: {
// 只要 Vue data 的值有改變時,不管是哪一個值改變,都會觸發每一個函式重新計算。
getNow: function() {
console.log("getNow" + "--被觸發執行了")
return Date.now();
},
plus() {
console.log("plus" + "----被觸發執行了")
this.count += 1;
}
},
computed: {
// 其相依的 `data` 改變時,computed 才會重新計算。
// 下面 now 函式裡面沒有 count 的變數,所以count更新時,也不會被觸發
now: function() {
console.log("now" + "-----被觸發執行了")
return Date.now();
}
},
});
</script>

範例程式:JSBin


Computed 與 Watch 的差異

觸發時機

  • Computed:當 methods 裡,有包含到 Vue Data 的變數的話,當變數值更新時,就會觸發 Computed 裡 method 的呼叫。

  • Watch:針對 Vue Data 裡,特別指定監聽某一個變數,當此變數更新時,就會觸發 Watch 裡 method 的呼叫。

說明

在一般情況下,我們通常會直接使用Computed去監聽變數,不用Watch是因為還需要一個個變數去綁定,
不過有時如果遇到要處理非同步複雜的計算時,就可以考慮使用Watch來處理事情。

Watch 範例

我想要「使用 trigger 來觸發旋轉 box、並在三秒後改變回來」,所以我特別指定監聽 trigger 這個變數是否有變更。

Watch 範例:JSBin

官方說明:watcher

template 小技巧

以往我們要針對多個DOM 新增v-if判斷時,需要一個一個新增,如下例

1
2
3
4
5
6
7
8
<div v-if="showTemplate">
<p>1</p>
<p>Kanboo</p>
</div>
<div v-if="showTemplate">
<p>2</p>
<p>Lucas</p>
</div>

若是多個 DOM 的條件都為一樣的話,此時我們可以使用template標籤,
將整個區塊包起來,然後在template上,新增判斷式即可。

1
2
3
4
5
6
7
8
9
10
<template v-if="showTemplate">
<div>
<p>1</p>
<p>Kanboo</p>
</div>
<div>
<p>2</p>
<p>Lucas</p>
</div>
</template>

DOM 解析注意

某些 HTML 元素限制內部的元素只能是某幾種 tag,
如:table、select、ul / ol 等,若是使用 Component 時就必須特別小心。

  • table => thead、tbody => tr => td
  • select => option
  • ul、ol => li

失效範例

雖然 select 底下的 Component my-option 的裡面是使用option看似合理,
不過最主要是一開始 HTML 在渲染時,看到select底下的是 my-option 標籤,
因為 HTML 認為這在select 底下是無效的標籤,所以就將my-option 標籤移除,
也因為這樣 Vue 要在解析 Component 時,已經不見了。

1
2
3
4
5
<div id="app">
<select>
<my-option></my-option>
</select>
</div>
1
2
3
4
5
6
7
Vue.component('my-option', {
template: `<option>Option A</option>`
});

var app = new Vue({
el: '#app'
});

正確方法

若要可以正常渲染的話,就需將 Component 寫法移至 Vue 的 template 裡面,
透過這樣的方式,讓 Vue 來幫我們解析及渲染畫面。

1
<div id="app"></div>
1
2
3
4
5
6
7
8
9
10
11
12
Vue.component('my-option', {
template: `<option>Option A</option>`
});

var app = new Vue({
el: '#app',
template: `
<div id="app">
<select><my-option></my-option></select>
</div>
`
});

資料更新的機制

一般情況下,我們要更新 Vue Data 裡面的值時,通常直接指定變數後,就賦予值給它,
不過若是要更新「物件 和 陣列」時,就要注意什麼情況會更新,什麼情況不會被更新,
下列範例主要是看 PJ 大文章說明(為什麼畫面沒有隨資料更新 - Vue 響應式原理(Reactivity)),
將一些關鍵點紀錄下來

物件更新

若是物件要更新某個屬性值的話,有下列方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const postVm = new Vue({
el: '#post',
data: {
post: {
userId: '',
id: '',
title: ''
// body: "", //一開始沒加
// author: "" //一開始沒加
}
},
created() {
let vm = this;
// https://jsonplaceholder.typicode.com/posts/1
request.get(root + '/posts/3').end((err, res) => {
let response = res.body;
/**
* 問題狀況
* 1. vm.post.body 一開始忘記設定進去
* 2. vm.post.author 後來新增的欄位
* 資料已經設定進去,但是畫面沒有更新,可以開 Vue Dev Tool 搭配 $forceUpdate()
**/
vm.post.userId = response.userId;
vm.post.id = response.id;
vm.post.title = response.title;
// ↓ 事後新增此變數,畫面會無法自動刷新,主要是沒有賦予響應式屬性(Reactivity)。
vm.post.body = response.body;

/**
* 解決辦法 1: 一開始在 data 的地方就補齊
**/

/**
* 解決辦法 2: 使用 Vue.set(object, key, value),重新賦予響應式屬性(Reactivity)
**/
vm.$set(vm.post, 'body', response.body);
vm.$set(vm.post, 'author', '');

/**
* 解決辦法 3: 使用 Object.assign 建立新的物件
**/
// vm.post = response // 不建議
vm.post = Object.assign({}, response, { author: '' });
});
}
});

陣列更新

若針對陣列時,只有下列幾個 methods,才會觸發 Vue 去重新渲染畫面。

陣列函式:push()、pop()、shift()、unshift()、splice()、sort()、reverse()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
const postsVm = new Vue({
el: '#posts',
data: {
currentIndex: '',
posts: [],
post: {
userId: '',
id: '',
title: '',
body: ''
}
},
methods: {
refreshForm(index) {
this.currentIndex = index; // 暫存用變數
this.post.userId = this.posts[index].userId;
this.post.id = this.posts[index].id;
this.post.title = this.posts[index].title;
this.post.body = this.posts[index].body;
},
saveForm(event) {
console.log('saveForm');
console.log('this.currentIndex', this.currentIndex);
/**
* 問題一:
* 使用 index 的方式修改陣列中的內容會壞掉
* - 資料更新但畫面不更新
**/
this.posts[this.currentIndex] = this.post;

/**
* 解決方法一:使用 Vue 可觀察到的陣列方法
* push()、pop()、shift()、unshift()、splice()、sort()、reverse()
* arr.splice(startIndex, delteCount, addItem)
**/
this.posts.splice(this.currentIndex, 1, this.post); // 會有陣例 by reference 問題

/**
* 解決方法二: 使用 vm.$set
* vm.$set(array, index, value)
**/
this.$set(this.posts, this.currentIndex, this.post); // 會有陣例 by reference 問題

/**
* 問題二:陣列中的值是「物件內容」變成響應式的,會有by reference,
* 所以若是遇到「物件」的話,建議使用 Object.assign 重新賦予
**/

// 對應 解法一
this.posts.splice(
this.currentIndex,
1,
Object.assign({}, this.post) // 重新創一個新物件
);

// 對應 解法二
this.$set(this.posts, this.currentIndex, Object.assign({}, this.post));
}
},
computed: {
// postsLength () {
// // 提醒:如果畫面中沒有使用到 postsLength ,則不會自動促發 computed
// let newPostId = this.posts.length + 1
// this.post.id = newPostId
// return newPostId
// }
},
created() {
let vm = this;
// AJAX request
// https://jsonplaceholder.typicode.com/posts/
request.get(root + '/posts').end((err, res) => {
vm.posts = res.body;
});
}
});

Vue 生命週期

建立或移除 DOM 元素時,所經歷的事件

  1. beforeCreate:實體初始化。
  2. Created:實體建立完成。資料 $data 已可取得,但 $el 屬性還未被建立。
    ———–↓↓↓————–
  3. beforeMount:執行元素掛載之前。
  4. mounted:元素已掛載, $el 被建立。
    ———–↓↓↓————–
  5. beforeUpdate:當資料變化時被呼叫,還不會描繪 View。
  6. updated:DOM 的更新已經完成,View 被顯示在畫面上。
    ———–↓↓↓————–
  7. activated:如果有設定 keep-alive,這個掛鉤會被呼叫。
  8. deactivated:停用 keep-alive時被呼叫。
  9. beforeDestroy:實體還可使用。
  10. destroyed:實體銷毀。所有綁定被解除、事件偵聽被移除、子實體也被銷毀。

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
beforeCreate() {
console.log(`beforeCreate! ${this.text}`);
},
created() {
alert(`created! ${this.text}`);
},

beforeMount() {
alert(`beforeMount! ${this.text}`);
},
mounted() {
alert(`mounted! ${this.text}`);
},

updated () {
console.log(`updated! ${this.text}`);
},

activated () {
// 有使用 keep-alive 時,會觸發此事件
alert(`activated! ${this.text}`);
},
deactivated () {
// 有使用 keep-alive 時,當使用v-if為false,
alert(`deactivated! ${this.text}`);
},

beforeDestroy() {
// 刪除前
console.log(`beforeDestroy! ${this.text}`);
},
destroyed() {
// 刪除後
console.log(`destroyed! ${this.text}`);
}

生命週期範例 1:JSBin
生命週期範例 2:JSBin