Vue3 Options API(傳統寫法) — data() 完全指南
簡介
在 Vue 3 中,雖然 Composition API 成為了新手教學的主流,但 Options API 仍然是許多既有專案以及剛入門的開發者最熟悉的寫法。data() 作為 Options API 的核心入口,負責定義組件的 響應式狀態(state),也是所有 Vue 組件最先被執行的鉤子之一。
掌握 data() 的使用方式與注意細節,能讓你在開發過程中快速建立可維護、易除錯的 UI,尤其在以下情境更為關鍵:
- 表單與輸入驗證:需要即時反映使用者輸入的變化。
- 列表渲染:大量資料的動態增減必須保持效能。
- 跨組件共享狀態:透過父子傳值或 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。 |
最佳實踐:
- 最小化
data內容:只存放 UI 必須響應的狀態。 - 保持資料型別一致:例如
v-model.number、v-model.boolean,避免因型別不符導致渲染錯誤。 - 使用
created/mounted初始化非同步資料,讓data只負責「資料結構」而非「取得資料」的流程。 - 命名規則:
data屬性建議使用 駝峰式(如userName)或 全小寫加底線(如user_name),保持與方法 (methods) 的區別。 - 分層管理:若組件變得過於龐大,將部分狀態抽到 Vuex/Pinia 或 provide/inject,讓
data保持簡潔。
實際應用場景
表單編輯器
- 使用
data定義表單欄位的初始值,配合v-model完成雙向綁定。 - 結合
watch監聽欄位變化,實作即時驗證或自動儲存功能。
- 使用
動態列表(Todo、購物車)
data中的陣列 (items) 讓 Vue 能自動觀測push、splice等變更。- 搭配
computed計算總金額或未完成項目數,保持 UI 與資料同步。
儀表板(Dashboard)
- 在
created或mounted中呼叫 API,將回傳的統計資料寫入data,讓圖表元件自動重繪。 - 使用
loading、error等旗標控制 UI 狀態,提升使用者體驗。
- 在
父子組件溝通
- 父層透過
props傳入只讀資料,子層在data中建立本地副本以進行編輯。 - 編輯完成後透過
$emit把變更回傳父層,保持單向資料流的可預測性。
- 父層透過
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 生態系中最堅實的基礎。祝開發順利,玩得開心! 🚀