本文 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 前執行,之後依序觸發 created、mounted 等生命週期。這意味著在 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. data 與 props 的互動

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

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,務必先在 data 或 computed 中建立副本,避免直接改變只讀屬性。


範例 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 會在開發模式拋出警告,且可能破壞單向資料流。 使用 computed、watch 或在 data 中建立本地副本。
深層物件未被觀測 若在 data 之後才新增深層屬性,Vue 2 需要 this.$set,Vue 3 已支援 Proxy,但仍需注意陣列索引變更。 使用 Vue.set(Vue 2)或直接賦值(Vue 3),確保屬性在 data 初始化時已存在。
在 data 中使用 this data 執行時 this 尚未指向組件實例,會得到 undefined。 只能使用外部變數或在 created、mounted 中使用 this。
大型物件直接放入 data 每次組件重新渲染都會深度觀測,可能影響效能。 只保留需要響應的屬性,其他大資料可放在 setup()(若混用 Composition)或外部 store。

最佳實踐:

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

實際應用場景

  1. 表單編輯器

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

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

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

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

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

總結

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

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

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