Vue3 Options API(傳統寫法)— methods 完全指南
簡介
在 Vue3 中,Options API 仍然是許多既有專案與新手入門的首選寫法。methods 是 Options API 的核心之一,用來定義元件內可被呼叫的函式,負責處理使用者互動、資料運算或與外部 API 溝通等行為。
掌握 methods 的正確寫法與最佳實踐,能讓你的元件 易於維護、行為可預測,同時避免常見的 this 綁定錯誤或效能問題。本文將從概念說明、實作範例、常見陷阱到實務應用,帶你一步步深入了解 Vue3 Options API 中的 methods。
核心概念
1. methods 的基本結構
在 Options API 中,methods 是一個普通的物件,鍵名為方法名稱,值為函式本身。Vue 會自動把這些函式掛在元件實例上,使得在模板 (template) 或其他生命週期鉤子中都能以 this.methodName() 方式呼叫。
export default {
data() {
return {
count: 0
}
},
methods: {
// 增加 count
increment() {
this.count++
}
}
}
重點:
methods中的函式不會自動被 Vue 轉成響應式,只有在它們內部操作data、computed或ref時,才會觸發視圖更新。
2. this 的指向
methods 內的函式在呼叫時,this 永遠指向當前的 Vue 元件實例。因此 不要使用箭頭函式(=>)定義 methods,否則 this 會被綁定到外層作用域,導致無法正確存取 data、props 等。
methods: {
// ✅ 正確寫法
addItem(item) {
this.items.push(item)
},
// ❌ 錯誤寫法(this 不是 Vue 實例)
// addItem: (item) => {
// this.items.push(item) // 會拋出錯誤
// }
}
3. 參數與回傳值
methods 可以接受任意數量參數,並回傳值給呼叫端。這讓你可以在模板內直接使用返回結果(雖然不建議在模板中寫大量邏輯),或在其他方法、生命週期鉤子中取得計算結果。
methods: {
/** 計算兩數之和 */
sum(a, b) {
return a + b
}
}
4. 非同步方法
在與後端 API 互動時,常會使用 async/await 或 Promise。methods 完全支援非同步函式,只要確保在呼叫端正確處理回傳的 Promise 即可。
methods: {
/** 取得使用者資料 */
async fetchUser(id) {
try {
const response = await fetch(`https://api.example.com/users/${id}`)
const data = await response.json()
this.user = data
} catch (err) {
console.error('取得使用者失敗', err)
}
}
}
程式碼範例
以下提供 5 個實用範例,涵蓋從基本操作到進階非同步、事件傳遞與 ref 操作,幫助你快速上手。
範例 1️⃣:簡單的計數器方法
export default {
data() {
return {
count: 0
}
},
methods: {
/** 點擊按鈕時 +1 */
increment() {
this.count++
},
/** 點擊按鈕時 -1 */
decrement() {
this.count--
}
}
}
在模板中:
<button @click="increment">+</button>
<span>{{ count }}</span>
<button @click="decrement">-</button>
範例 2️⃣:帶參數的搜尋過濾
export default {
data() {
return {
query: '',
items: ['Vue', 'React', 'Angular', 'Svelte'],
filtered: []
}
},
methods: {
/** 根據 query 篩選 items */
filterItems(keyword) {
this.filtered = this.items.filter(item =>
item.toLowerCase().includes(keyword.toLowerCase())
)
},
/** 直接在 input 事件中傳入 $event.target.value */
onInput(event) {
this.filterItems(event.target.value)
}
}
}
模板:
<input type="text" v-model="query" @input="onInput" placeholder="搜尋框">
<ul>
<li v-for="item in filtered" :key="item">{{ item }}</li>
</ul>
範例 3️⃣:非同步取得資料並顯示 loading 狀態
export default {
data() {
return {
posts: [],
loading: false,
errorMsg: ''
}
},
methods: {
/** 取得部落格文章 */
async loadPosts() {
this.loading = true
this.errorMsg = ''
try {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
if (!res.ok) throw new Error('Network response was not ok')
this.posts = await res.json()
} catch (e) {
this.errorMsg = e.message
} finally {
this.loading = false
}
}
},
// 進入元件時自動載入
created() {
this.loadPosts()
}
}
模板:
<div v-if="loading">載入中…</div>
<div v-else-if="errorMsg" class="error">{{ errorMsg }}</div>
<ul v-else>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
範例 4️⃣:使用 $refs 操作 DOM(如清空輸入框)
export default {
methods: {
/** 清空文字輸入框 */
clearInput() {
// this.$refs.myInput 取得 DOM 元素
this.$refs.myInput.value = ''
// 若需要同步 data,也一起更新
this.inputText = ''
}
},
data() {
return {
inputText: ''
}
}
}
模板:
<input type="text" v-model="inputText" ref="myInput" placeholder="輸入文字">
<button @click="clearInput">清空</button>
範例 5️⃣:自訂事件 ($emit) 結合父子元件
子元件 (Child.vue)
export default {
props: {
/** 按鈕顏色 */
color: { type: String, default: 'primary' }
},
methods: {
/** 點擊時向父元件發射自訂事件 */
notifyParent() {
// 這裡可以攜帶任意資料
this.$emit('clicked', { time: Date.now(), color: this.color })
}
}
}
子元件模板:
<button :class="color" @click="notifyParent">點我</button>
父元件 (Parent.vue)
export default {
methods: {
/** 接收子元件的 clicked 事件 */
handleChildClick(payload) {
console.log('子元件點擊資訊:', payload)
// 例如更新父層的狀態
this.lastClickInfo = payload
}
},
data() {
return {
lastClickInfo: null
}
}
}
父元件模板:
<Child color="danger" @clicked="handleChildClick" />
<div v-if="lastClickInfo">
最後一次點擊時間:{{ new Date(lastClickInfo.time).toLocaleTimeString() }}
</div>
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 使用箭頭函式定義 methods | this 會指向外層作用域,導致無法存取 data、props 等。 |
使用 普通函式 (function 或簡寫方法) 定義。 |
| 在模板內大量運算 | 會在每次渲染時重新執行,影響效能。 | 把計算邏輯搬到 computed 或 methods,僅在需要時呼叫。 |
| 直接修改 props | Props 為單向資料流,直接改變會觸發 Vue 警告。 | 透過 $emit 或在子元件內使用本地 data 進行副本修改。 |
| 在 methods 中使用非同步程式碼卻未處理錯誤 | 例外會被吞掉,難以除錯。 | 使用 try...catch 或 .catch() 捕獲錯誤,並提供使用者回饋。 |
| 方法過於龐大、職責不單一 | 難以維護、測試困難。 | 遵守單一職責原則:每個 method 只做一件事,必要時拆分成子方法。 |
| 在 methods 中直接操作 DOM(除非真的需要) | 破壞 Vue 的抽象層,降低可測試性。 | 儘量使用 Vue 的指令 (v-show、v-if) 或 ref,僅在特殊需求時才操作原生 DOM。 |
最佳實踐小結
- 保持方法簡潔:每個 method 只處理單一任務,易於閱讀與測試。
- 避免箭頭函式:確保
this正確指向 Vue 實例。 - 使用
async/await:讓非同步流程更直觀,同時配合錯誤處理。 - 命名慣例:使用 camelCase 命名方法,並在事件名稱上使用 kebab-case(如
@click、@my-event)。 - 適時使用
$emit:子元件與父元件溝通時,透過自訂事件傳遞資料,保持單向資料流。 - 避免在模板中寫複雜邏輯:把邏輯搬到 methods 或 computed,保持模板乾淨。
實際應用場景
| 場景 | 為什麼使用 methods |
範例概念 |
|---|---|---|
| 表單驗證 | 需要在提交前執行多步檢查,且可能涉及非同步驗證(如 API 重名檢查)。 | validateForm()、checkUsername()(async) |
| 即時搜尋 | 使用者輸入時即時過濾資料,或向後端請求關鍵字建議。 | onSearchInput()、fetchSuggestions() |
| 圖表互動 | 按鈕切換圖表類型、更新資料來源。 | changeChartType()、reloadChartData() |
| 多語系切換 | 點擊語言切換按鈕,呼叫方法改變全局 i18n 設定。 | switchLocale() |
| 檔案上傳 | 點擊上傳按鈕後執行檔案讀取、驗證、上傳流程。 | handleFileSelect()、uploadFile()(async) |
實務建議:在上述情境中,將 UI 事件(如
@click)直接對應到methods,而把純資料處理(如篩選、格式化)抽出成 純函式(可於utils資料夾中)或 computed,能讓程式碼結構更清晰,也方便單元測試。
總結
methods 是 Vue3 Options API 中與使用者互動、執行業務邏輯的關鍵入口。
- 正確的 函式寫法(避免箭頭函式)確保
this指向元件實例。 - 透過 參數、回傳值 讓方法具備彈性,配合 async/await 處理非同步需求。
- 最佳實踐(單一職責、錯誤處理、命名慣例)與 常見陷阱(DOM 直接操作、props 直接改寫)是提升專案品質的核心。
掌握以上概念與範例,你就能在 Vue3 專案中 快速、穩定 地實作各種功能,從簡單的計數器到複雜的表單驗證與 API 串接,都能游刃有餘。祝開發順利,持續寫出乾淨、可維護的 Vue 元件!