本文 AI 產出,尚未審核

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 轉成響應式,只有在它們內部操作 datacomputedref 時,才會觸發視圖更新。

2. this 的指向

methods 內的函式在呼叫時,this 永遠指向當前的 Vue 元件實例。因此 不要使用箭頭函式=>)定義 methods,否則 this 會被綁定到外層作用域,導致無法正確存取 dataprops 等。

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 或簡寫方法) 定義。
在模板內大量運算 會在每次渲染時重新執行,影響效能。 把計算邏輯搬到 computedmethods,僅在需要時呼叫。
直接修改 props Props 為單向資料流,直接改變會觸發 Vue 警告。 透過 $emit 或在子元件內使用本地 data 進行副本修改。
在 methods 中使用非同步程式碼卻未處理錯誤 例外會被吞掉,難以除錯。 使用 try...catch.catch() 捕獲錯誤,並提供使用者回饋。
方法過於龐大、職責不單一 難以維護、測試困難。 遵守單一職責原則:每個 method 只做一件事,必要時拆分成子方法。
在 methods 中直接操作 DOM(除非真的需要) 破壞 Vue 的抽象層,降低可測試性。 儘量使用 Vue 的指令 (v-showv-if) 或 ref,僅在特殊需求時才操作原生 DOM。

最佳實踐小結

  1. 保持方法簡潔:每個 method 只處理單一任務,易於閱讀與測試。
  2. 避免箭頭函式:確保 this 正確指向 Vue 實例。
  3. 使用 async/await:讓非同步流程更直觀,同時配合錯誤處理。
  4. 命名慣例:使用 camelCase 命名方法,並在事件名稱上使用 kebab-case(如 @click@my-event)。
  5. 適時使用 $emit:子元件與父元件溝通時,透過自訂事件傳遞資料,保持單向資料流。
  6. 避免在模板中寫複雜邏輯:把邏輯搬到 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 元件!