跳至主要内容

[Vue] Options API

TL;DR

Vue3提供Options API 跟新增的 Composition API

初學者學習Options API比較好,因為目前專案仍大多是使用Options API,且Vue2也是使用Options API。

箭頭函式

在使用Options API時會頻繁使用this

在使用箭頭函式時要特別注意

參考資料

相關連結


methods

觸發方式

methods為一個物件,裡面存在許多的函式。調用methods內函式有許多的方法。

可以由v-on觸發,由其他methods內的方法觸發,也可以由生命週期觸發。

<div id="app">
<div class="p-5">
<h3>methods 的結構</h3>
<ul>
<li>由 methods 定義的物件</li>
<li>內層均是函式</li>
</ul>

<h3>methods 的觸發方法(點擊、其它 options api、生命週期...)</h3>
<button type="button" @click="trigger('Click Methods')">點擊觸發</button>

<button type="button" @click="callOtherMethod" class="ms-1">呼叫另一個 methods</button>
</div>
</div>

傳入參數

當使用v-on指令觸發methods時,我們可以使用一個特殊的變數$event,將事件物件傳入方法中。

<button @click="functionName('參數一',$event)">傳入$event</button>
只有一個變數的method

如果這個function只有一個變數會傳入,則在使用v-on觸發時不帶小括號,該function的變數會自動為事件物件

<button @click="functionName">點擊會自動傳入事件物件</button>

處理複雜資料

<div id="app">
<div class="p-5">
<div id="app">
<h3>使用 methods 處理複雜資料</h3>
<ul>
<li v-for="product in products">
{{ product.name }} / {{ product.price }}
<button type="button" @click="addToCart(product)">加入購物車</button>
</li>
</ul>
<h6>購物車項目</h6>
<ul>
<li v-for="item in carts">{{ item.name }}</li>
</ul>
總金額 {{ sum }}
<h3>作為 $filter 使用(取代複雜表達式)</h3>
總金額 {{ convertToAmount(sum) }}
</div>
</div>
</div>

Line:6 ~ Line:9 使用v-for 渲染出所有選項,並且在每個選項按鈕上綁定相對應的@click,將該項目的product物件傳入addToCart函式中。

Line:13 中我們事先準備好一個li,同樣使用v-for迴圈渲染出購物車的項目。每當購物車carts內的內容有新增,就會渲染到畫面上。

Line:15中,我們直接在畫面上渲染{{ sum }}的值。這個代表的是所有購物車內的價格計算加總出來的結果
但是我們需要觸發讓他可以計算出對應的價格,並且存入datasum內。
這邊我們在每次點擊按鈕新增資料到購物車內時,調用計算價格的函式,並且存入資料中。

const App = {
data() {
return {
products: [
{
name: '蛋餅',
price: 30,
vegan: false
},
...
],
carts: [],
sum: 0,
}
},
methods: {
addToCart(product) {
this.carts.push(product)
this.calculate();
},
calculate() {
let total = 0;
this.carts.forEach(item => {
total += item.price;
});
this.sum = total;
},
...
},
created() {}
};
Vue.createApp(App).mount('#app');
filter

以前在vue2時有一個filter 的 API,現在可以直接使用methods取代。

在methods中撰寫一個function:

convertToAmount(price) {
return `NT$ ${price}`;
}

該方法會回傳一個組合好的字串

並且在畫面上直接在{{}}內插入表達式,去調用這個function,將data內的num值給傳入

總金額 {{ convertToAmount(sum) }}

結果就會顯示出

總金額 NT$ 125

computed

語法

computed是為了簡化在渲染時有過多的邏輯。computed為一個物件,物件內宣告的函式名稱即為調用名稱。調用時的值為函式內的回傳值。

使用computed的注意事項

在使用computed API的時候,要避免在函式內去直接更動資料(但是仍然有提供setter的方式,詳見後方),也不要有非同步的行為在內。

另外在computed內具有快取的機制,只要資料本身沒有變動,則computed所回傳出來的內容都會直接取用快取的值。

<div id="app">
<div class="p-5">
{{foo}} / {{typeof foo}}
<!-- 100 / string -->
</div>
</div>

使用範例

前面有使用methods計算出total的值,這邊改用computed去改寫:

<div id="app">
<div class="p-5">
<h3>Computed 將目前的數值運算呈現至畫面上</h3>
<ul>
<li v-for="product in products">
{{ product.name }} / {{ product.price }}
<button type="button" @click="addToCart(product)">加入購物車</button>
</li>
</ul>
...
<h6>購物車項目</h6>
<ul>
<li v-for="item in carts">{{ item.name }}</li>
</ul>
total 的值:{{total}} <br>
</div>
</div>

也可以在return的時候使用樣板字面值:

const App = {
data() {
return {...}
},
methods: {...},
computed: {
total() {
let total = 0;
this.carts.forEach(item => {
total += item.price;
});
return `總共 NTD:${total}`;
}
}
};
Vue.createApp(App).mount('#app');

搜尋應用

<div id="app">
<div class="p-5">
<h3>Computed 常見技巧 - 搜尋</h3>
<input type="search" v-model="search">
<ul>
<li v-for="item in filterProducts">
{{ item.name }} / {{ item.price }}
</li>
</ul>
<hr>
<p v-pre>{{filterProducts}}的內容</p>
{{filterProducts}}
</div>
</div>

在computed內定義一個filterProducts的函式,該函式的回傳值會是與search資料比對後回傳的陣列。

因為search的值與input透過v-model雙向綁定,所以filterProducts所回傳的陣列可以動態改變。

因為filterProducts回傳的是陣列,所以可以使用v-for來回圈渲染所需要的內容。

getter/setter

前面所介紹的其實是computed的getter用法

computed另外還有setter的用法。

getter

要啟用computed的setter,首先必須先將函式改為物件,並且在裡面加上get()set()

const App = {
data() {
return {...}
},
methods: {...},
computed: {
total: {
get() {
let total = 0;
this.carts.forEach(item => {
total += item.price;
});
return total;
},
set(val) {
...
}
},
},
};
Vue.createApp(App).mount('#app');

totla在函式寫法時,回傳的值即為getter的值。

我們將原本的內容調整到total物件內的get函式中,運作會目前會完全相同。

setter

setter顧名思義,與getter是相反的。

getter會將資料取出,運算後渲染到畫面上。而setter則是可以將資料運算後再存回。

<div id="app">
<div class="p-5">
<ul>
<li v-for="product in products">
{{ product.name }} / {{ product.price }}
<button type="button" @click="addToCart(product)">加入購物車</button>
</li>
</ul>
...
<h6>購物車項目</h6>
<ul>
<li v-for="item in carts">{{ item.name }}</li>
</ul>
total 的值:{{total}} <br>
<h3>Computed Getter, Setter</h3>
sum 的值:
<input type="number" v-model.number="num">
<button type="button" @click="total = num">更新</button>
total 的值:{{ total }}<br>
sum 的值:{{ sum }}
</div>
</div>

watch

watchcomputed的差異在於:

  1. 監聽變數的數量
    1. computed可以同時監聽多個變數
    2. watch只能監聽單一個變數
  2. 反應結果的方式不同
    1. computed利用return直接回傳運算結果
    2. watch透過this.data寫入資料反應運算結果
  3. 初始化時是否會運算結果
    1. computed在初始化時就會有一個return的值
    2. watch必須要等到第一次資料有變動才會執行裡面的運算
使用時機

如果需要監聽多個變數並計算出一個結果,需使用computed

只需監聽一個變數但是要動態調整多筆資料則是使用watch

語法

watch與computed相同,為一個物件,物件內所宣告的函示名稱即為欲監聽的資料變數名稱。該函示可以帶入兩個參數,分別為變動過後的值變動前的值

<div id="app">
<div class="p-5">
<input type="number" v-model.lazy.number="foo">
{{foo}} / {{bar}}
</div>
</div>

使用範例

<div id="app">
<div class="p-5">
<h3>watch 監聽單一變數</h3>
<label for="name">名字須超過十個字</label>
<input type="text" id="name" v-model="tempName">
<P>result: {{ result }}</P>
<p>name: {{ name }}</p>

<label for="productName">商品名稱</label><input type="text" v-model="productName"><br>
<label for="productPrice">商品價格</label><input type="number" v-model.number="productPrice"><br>
<label><input type="checkbox" v-model="productVegan"> 素食</label>
<p>Computed result2: {{ result2 }}</p>
<p>Watch result3: {{ result3 }}</p>

<h3>watch 深層監聽</h3>
<label for="productName">商品名稱</label><input type="text" v-model="product.name"><br>
<label for="productPrice">商品價格</label><input type="number" v-model.number="product.price"><br>
<label><input type="checkbox" v-model="product.vegan"> 素食</label>
<p>result4: {{ result4 }}</p>
</div>
</div>

在vue中的 Line:15 ~ Line:19 中,computed可以一次監聽productName,productPrice,productVegan三個變數。但是只能回傳出一個字串並賦予到result2中。

而vue中的 Line:21 ~ Line:28 雖然只能監聽tempName一個變數,但是根據監聽變數的變化,同時操作this.resultthis.name兩個變數。

深層監聽

如果資料為物件的形式,希望在該物件內有任何資料變動都會即時反應,則可以使用深層監聽。

<div id="app">
<div class="p-5">
<h3>watch 深層監聽</h3>
<label for="productName">商品名稱</label><input type="text" v-model="product.name"><br>
<label for="productPrice">商品價格</label><input type="number" v-model.number="product.price"><br>
<label><input type="checkbox" v-model="product.vegan"> 素食</label>
<p>result4: {{ result4 }}</p>
</div>
</div>

其中,以下格式為固定的結構:

watch:{
watchedObjectName:{
handler(n,o){
...
},
deep:true
}
}

只要watchedObjectName內的任何資料有變動,都會觸發這個watch API。

深層監聽的new,old是同一個對象

官方文件內有提到,深層監聽的物件中newValue跟oldValue會是相等的。這是因為他們是同一個對象(call by reference)

因為當深層監聽的對象是物件或陣列時,Vue 是根據 Reference(記憶體位置)來判斷是否相同

而觸發監聽的時候,監聽對象的 Reference 不會改變,所以新值與舊值都會是同一個物件

另外也有說明要謹慎使用深層監聽,因為會需要花費許多的資源(去監聽裡面的每一個屬性)