本文 AI 產出,尚未審核

Vue3 Options API(傳統寫法) — data() 完全指南


簡介

在 Vue 3 中,雖然 Composition API 成為了新手教學的主流,但 Options API 仍然是許多既有專案以及剛入門的開發者最熟悉的寫法。data() 作為 Options API 的核心入口,負責定義組件的 響應式狀態(state),也是所有 Vue 組件最先被執行的鉤子之一。

掌握 data() 的使用方式與注意細節,能讓你在開發過程中快速建立可維護、易除錯的 UI,尤其在以下情境更為關鍵:

  1. 表單與輸入驗證:需要即時反映使用者輸入的變化。
  2. 列表渲染:大量資料的動態增減必須保持效能。
  3. 跨組件共享狀態:透過父子傳值或 Vuex/Pinia 前置資料。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,一路帶你深入了解 data(),並提供可直接套用於專案的範例程式碼。


核心概念

1. data() 的基本結構

在 Options API 中,data 必須是一個 函式,回傳一個純粹的物件。Vue 會在組件實例化時呼叫這個函式,將回傳的屬性轉換成 響應式(reactive)資料。

export default {
  // 必須是函式,不能直接寫成物件
  data() {
    return {
      message: 'Hello Vue 3!',
      count: 0,
      items: []               // 陣列同樣會被觀測
    }
  }
}

⚠️ 為什麼要是函式?
若直接以物件寫法(data: {}),所有使用該組件的實例會共享同一份資料,導致狀態相互干擾。函式保證每個實例都有自己的獨立資料副本。

2. 初始化與生命週期

data() 會在 beforeCreate 前執行,之後依序觸發 createdmounted 等生命週期。這意味著在 created 內即可存取 data 中的屬性,但在 beforeCreate 時尚未可用。

export default {
  data() {
    return { name: 'Alice' }
  },
  created() {
    console.log('created ->', this.name) // Alice
  },
  beforeCreate() {
    // console.log(this.name) // undefined
  }
}

3. 何時使用函式返回值的「深拷貝」

如果 data 中包含 物件或陣列,Vue 會使用 reactive(深層觀測)包裝它們。但若你在 data() 裡直接引用外部變數,會變成 共享引用,必須自行深拷貝。

const shared = { foo: 'bar' }

export default {
  data() {
    // ❌ 這樣會共享同一個物件
    // return { config: shared }

    // ✅ 深拷貝確保每個實例獨立
    return { config: JSON.parse(JSON.stringify(shared)) }
  }
}

4. dataprops 的互動

props 是父層傳入的只讀資料,不應在 data 中直接複製,因為會失去雙向同步的特性。若需要在本地修改,可使用 computedwatch 來產生可變的本地副本。

export default {
  props: {
    initialCount: Number
  },
  data() {
    return {
      // ❌ 直接複製會造成資料不同步
      // count: this.initialCount

      // ✅ 使用 computed 取得只讀值,或在 created 中初始化本地副本
      localCount: this.initialCount
    }
  }
}

5. 取得 data 中的屬性

在 Options API 中,data 內的屬性會被 代理 到組件實例 (this) 上,使用上與普通屬性無異。

export default {
  data() {
    return { message: 'Vue' }
  },
  methods: {
    greet() {
      alert(this.message) // 直接使用 this.message
    }
  }
}

程式碼範例

以下提供 五個實用範例,展示 data() 在不同情境下的寫法與技巧。

範例 1:基本表單雙向綁定

<template>
  <form @submit.prevent="submitForm">
    <label>姓名:
      <input v-model="name" placeholder="請輸入姓名" />
    </label>
    <p>你輸入的姓名是:<strong>{{ name }}</strong></p>
    <button type="submit">送出</button>
  </form>
</template>

<script>
export default {
  data() {
    return {
      // 文字輸入的雙向綁定屬性
      name: ''
    }
  },
  methods: {
    submitForm() {
      alert(`送出姓名:${this.name}`)
    }
  }
}
</script>

重點v-model 會自動將 input 的值與 data 中的 name 同步,無需額外事件監聽。


範例 2:陣列的增刪與列表渲染

<template>
  <div>
    <button @click="addItem">加入項目</button>
    <ul>
      <li v-for="(item, idx) in items" :key="item.id">
        {{ idx + 1 }}. {{ item.text }}
        <button @click="removeItem(idx)">✕</button>
      </li>
    </ul>
  </div>
</template>

<script>
let idCounter = 1

export default {
  data() {
    return {
      items: []   // 觀測陣列
    }
  },
  methods: {
    addItem() {
      this.items.push({ id: idCounter++, text: `項目 ${idCounter}` })
    },
    removeItem(index) {
      this.items.splice(index, 1)
    }
  }
}
</script>

技巧:使用 :key 讓 Vue 能正確追蹤每筆資料的身份,避免不必要的 DOM 重繪。


範例 3:深層物件的雙向綁定(表單)

<template>
  <div>
    <h3>使用者資訊</h3>
    <label>姓名:
      <input v-model="user.name" />
    </label>
    <label>年齡:
      <input type="number" v-model.number="user.age" />
    </label>
    <pre>{{ user }}</pre>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 深層物件會被 Vue 自動遞迴觀測
      user: {
        name: '',
        age: null
      }
    }
  }
}
</script>

說明v-model.number 會自動將輸入值轉成數字,避免字串型別的陷阱。


範例 4:使用 props 初始化本地狀態

<template>
  <div>
    <p>父層傳入的起始值:{{ initial }}</p>
    <p>本地可變值:{{ localCount }}</p>
    <button @click="localCount++">+1</button>
  </div>
</template>

<script>
export default {
  props: {
    initial: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      // 在 created 時從 props 複製,保持獨立
      localCount: this.initial
    }
  }
}
</script>

最佳實踐:若需要在本地修改 props,務必先在 datacomputed 中建立副本,避免直接改變只讀屬性。


範例 5:在 created 中呼叫 API,將結果放入 data

<template>
  <div>
    <h3>隨機使用者</h3>
    <p v-if="loading">載入中…</p>
    <p v-else>{{ user.name.first }} {{ user.name.last }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: null,
      loading: true
    }
  },
  async created() {
    try {
      const res = await fetch('https://randomuser.me/api/')
      const json = await res.json()
      this.user = json.results[0]   // 直接寫入 data
    } finally {
      this.loading = false
    }
  }
}
</script>

要點:即使在非同步函式中,也可以直接修改 this 上的 data,Vue 會自動追蹤變化並更新畫面。


常見陷阱與最佳實踐

陷阱 說明 解決方式
data 不是函式 直接寫成物件會導致所有實例共享同一份資料。 確保 data() 返回 一個新物件。
直接修改 props Vue 會在開發模式拋出警告,且可能破壞單向資料流。 使用 computedwatch 或在 data 中建立本地副本。
深層物件未被觀測 若在 data 之後才新增深層屬性,Vue 2 需要 this.$set,Vue 3 已支援 Proxy,但仍需注意陣列索引變更。 使用 Vue.set(Vue 2)或直接賦值(Vue 3),確保屬性在 data 初始化時已存在。
data 中使用 this data 執行時 this 尚未指向組件實例,會得到 undefined 只能使用外部變數或在 createdmounted 中使用 this
大型物件直接放入 data 每次組件重新渲染都會深度觀測,可能影響效能。 只保留需要響應的屬性,其他大資料可放在 setup()(若混用 Composition)或外部 store。

最佳實踐

  1. 最小化 data 內容:只存放 UI 必須響應的狀態。
  2. 保持資料型別一致:例如 v-model.numberv-model.boolean,避免因型別不符導致渲染錯誤。
  3. 使用 created/mounted 初始化非同步資料,讓 data 只負責「資料結構」而非「取得資料」的流程。
  4. 命名規則data 屬性建議使用 駝峰式(如 userName)或 全小寫加底線(如 user_name),保持與方法 (methods) 的區別。
  5. 分層管理:若組件變得過於龐大,將部分狀態抽到 Vuex/Piniaprovide/inject,讓 data 保持簡潔。

實際應用場景

  1. 表單編輯器

    • 使用 data 定義表單欄位的初始值,配合 v-model 完成雙向綁定。
    • 結合 watch 監聽欄位變化,實作即時驗證或自動儲存功能。
  2. 動態列表(Todo、購物車)

    • data 中的陣列 (items) 讓 Vue 能自動觀測 pushsplice 等變更。
    • 搭配 computed 計算總金額或未完成項目數,保持 UI 與資料同步。
  3. 儀表板(Dashboard)

    • createdmounted 中呼叫 API,將回傳的統計資料寫入 data,讓圖表元件自動重繪。
    • 使用 loadingerror 等旗標控制 UI 狀態,提升使用者體驗。
  4. 父子組件溝通

    • 父層透過 props 傳入只讀資料,子層在 data 中建立本地副本以進行編輯。
    • 編輯完成後透過 $emit 把變更回傳父層,保持單向資料流的可預測性。
  5. SSR(伺服器端渲染)

    • 在伺服器端執行 data() 時,仍會產生獨立的資料物件,確保每個請求的狀態不會相互污染。

總結

data() 是 Vue 3 Options API 中最基礎、也是最重要的概念之一。它負責 建立響應式狀態,並透過 代理 讓開發者在組件內部以 this 直接存取。掌握以下要點,你就能寫出 乾淨、可維護且效能良好 的 Vue 組件:

  • data 必須是 函式,每個實例都有自己的副本。
  • 只在 data 中放置 需要觀測的屬性,避免大型物件造成不必要的效能開銷。
  • 不要直接修改 props,若需要本地可變版本,請在 datacomputed 中建立副本。
  • 使用 created / mounted 來處理非同步資料取得,讓 data 僅負責 資料結構
  • 針對 陣列與深層物件,善用 Vue 3 的 Proxy 觀測機制,同時注意鍵值 (:key) 的設定,以提升渲染效能。

透過本文的介紹與範例,你已具備在實務專案中靈活運用 data() 的能力。未來若需要更彈性的組件邏輯,亦可逐步探索 Composition API;但無論是哪種寫法,對 data() 的深入理解都將是你在 Vue 生態系中最堅實的基礎。祝開發順利,玩得開心! 🚀