Vue 3 實戰:掌握 Props 傳值技巧與 Data Validation 型別驗證
在 Vue 的 元件化架構 中,Props 是父元件向子元件傳遞資料的唯一官方管道。透過明確宣告 Props,Vue 才能精準區分哪些屬性是 資料輸入 ,哪些是單純的 屬性透傳 。
如何宣告 Props:使用 defineProps
在 <script setup> 中,我們使用內建函式 defineProps 來宣告元件接收的資料。
基礎陣列宣告
這是最簡單的方式,適合開發初期快速原型製作:
// defineProps(['name', 'age']);
const props = defineProps(['name', 'age']);
console.log(props.name);
console.log(props.age);物件宣告與型別檢查
為了大型專案的維護性,建議使用 物件形式。這不僅能規範資料型別,當傳入錯誤型別時,Vue 還會在瀏覽器控制台發出警告,幫助您提早發現錯誤。
defineProps({
name: {
type: String,
default: '',
},
age: {
type: Number,
default: 10,
},
});常用的驗證選項如下:
| 選項 | 說明 |
|---|---|
type | 型別,可用 String、Number、Boolean、Array、Object、Function |
default | 父層未傳入時的預設值;物件與陣列必須寫成工廠函式 |
required | 設為 true 時,父層未傳入會在主控台發出警告 |
validator | 自訂驗證函式,回傳 false 時發出警告 |
defineProps({
// 物件、陣列的預設值必須用函式回傳,避免所有元件實體共用同一份資料
skills: {
type: Array,
default: () => [],
},
size: {
type: String,
default: 'medium',
validator: (value) => ['small', 'medium', 'large'].includes(value),
},
});靜態與動態 Props 的應用場景
在 Template 綁定 Props 時,需注意 靜態 與 動態 的差異:
靜態:值是固定字串,不會隨資料變動。
html<!-- 傳入的是字串 "iPhone 17 Pro Max" --> <ProductCard title="iPhone 17 Pro Max" />動態:以
v-bind綁定變數、表達式、運算結果。html<!-- 傳入的是變數 currentPrice 的值 --> <ProductCard :price="currentPrice" />
數字、布林、陣列一定要加冒號
沒有冒號時,傳進去的一律是 字串。count="1" 得到的是字串 '1',disabled="false" 得到的是字串 'false'(在判斷式中會是 true)。要傳非字串的值,一律用 : 綁定。
命名慣例:camelCase 與 kebab-case
在 JavaScript 中宣告與存取用 camelCase,在模板上傳遞時用 kebab-case,Vue 會自動對應:
defineProps({ productName: String });<ProductCard product-name="iPhone 17 Pro Max" />重要規範:單向數據流 (One-Way Data Flow)
所有的 props 都遵循 單向數據 流的原則:
- 數據向下流動:父層狀態更新時,Props 會自動同步給子元件。
- 禁止逆向修改:子元件不應在內部直接修改 Props。這樣可以確保數據流向清晰,避免多個子元件同時修改父層狀態導致維護災難。
實戰提醒
如果您試圖在子元件內修改 Prop 值,Vue 會在主控台(Console)噴出警告。若需要修改該資料,請考慮使用 emit() 通知父層更新,或將其作為 ref() 的初始值轉化為元件內部狀態。
需要修改時的兩種正確作法
// 1. 只是想要一個以 prop 為初始值的內部狀態
const props = defineProps({ initialCount: Number });
const count = ref(props.initialCount);
// 2. 只是想加工後顯示,交給 computed
const formattedPrice = computed(() => `NT$ ${props.price.toLocaleString()}`);真的需要更動父層資料時,請改用 emit 通知父層,或用 defineModel 建立雙向綁定。
實戰範例
靜態傳遞
- https://cdsassets.apple.com/live/7WUAS350/images/iphone/iphone-17-pro-max-colors.png
- iPhone 17 Pro Max
動態傳遞
['html', 'rwd', 'javascript', 'vue', 'sass'];.bevel {
position: relative;
padding: 1rem;
color: #666;
line-height: 1;
list-style-type: none;
text-transform: uppercase;
border: solid 1px #ccc;
}
.bevel::before,
.bevel::after {
position: absolute;
width: 16px;
height: 16px;
background-color: #fff;
content: "";
transform: rotate(45deg);
}
.bevel::before {
top: -9px;
left: -9px;
border-right: solid 1px #ccc;
}
.bevel::after {
bottom: -9px;
right: -9px;
border-left: solid 1px #ccc;
}
.bevel + .bevel {
margin-top: 1rem;
}型別與驗證
{
name: 'Away',
fullName: () => `${user.name} Hung`,
age: 41,
isMale: true,
skills: ['RWD', 'CSS', 'Javascript', 'HTML'],
books: {
name1: 'ACA 國際認證教戰手冊:Dreamweaver CS5 完全攻略',
name2: 'Illustrator CS5(補習班內部用書)',
name3: 'Illustrator CS4(補習班內部用書)',
},
}動態傳遞與狀態控管
[
{
id: 1,
isRelease: false,
name: '梵蒂岡驅魔士',
},
{
id: 2,
isRelease: true,
name: '超級瑪利歐兄弟電影版',
},
{
id: 3,
isRelease: true,
name: '捍衛任務4',
},
{
id: 4,
isRelease: false,
name: '星際異攻隊3',
},
{
id: 5,
isRelease: true,
name: '鈴芽之旅',
},
];/* 基本樣式 */
.box {
padding: 0.7rem 1rem;
margin-bottom: 1rem;
}
/* 已上映 */
.box--release {
background-color: lightblue;
}
/* 未上映 */
.box--no-release {
color: #ccc;
background-color: #eee;
}常見問題
陣列宣告與物件宣告該用哪一種?
陣列宣告只寫得出名稱,適合快速原型;正式專案建議使用物件宣告,可以指定型別、預設值與必填,型別不符時 Vue 會在主控台提出警告。
為什麼傳數字給元件要加冒號?
沒有冒號時傳進去的一律是字串,寫 count="1" 得到的是字串 '1'。要傳數字、布林、陣列或物件,必須用 v-bind 綁定,寫成 :count="1"。
可以在子元件裡直接修改 props 嗎?
不行,這違反單向數據流,Vue 會發出警告。需要改動時,請用 emit 通知父層更新,或把 prop 當成初始值複製成元件自己的 ref。
物件或陣列型別的 default 為什麼要寫成函式?
因為物件與陣列是參考型別,直接寫成字面值會讓所有使用該元件的實體共用同一份資料。必須改寫成工廠函式,例如:default: () => [],每個實體才會拿到各自的副本。
prop 名稱要用駝峰還是連字號?
在 JavaScript 中宣告與存取用 camelCase,在模板上傳遞時用 kebab-case,例如:宣告 productName、傳遞寫成 product-name。Vue 會自動對應這兩種寫法。
props 與 attrs 差在哪裡?
有在 defineProps 宣告的才是 props;沒宣告的屬性會變成 透傳屬性 attrs,預設自動掛到子元件的根元素上。