Vue 3 用實作帶你看過核心概念 - Day 18:父組件向子組件傳值 - props
5.8k 字,約 6 分鐘
2024 iThome 鐵人賽:Vue 3 初學者:用實作帶你看過核心概念。 原文發表於 iThome,也可以在下方留言。
文章目錄
目錄
- props 基本用法
- props 單向資料流 - 組件之間的溝通方式
- props 子組件應用情境說明
- props 傳值類型 - 靜態傳值 vs 動態傳值
- props 多參數傳遞的方式
- props 校驗方式 - 檢查所有外來的參數
- props Boolean 型別規則判定方式
- 總結
props 基本用法
組件之間的傳值必須透過聲明 props 的方式,這樣可以避免與HTML的屬性(attribute)混淆。父組件傳遞參數給子組件的方式有以下兩種方式:
- 不限制傳入的 props 型別或行為:使用
陣列的方式來定義props,這樣只需列出父組件傳遞的props名稱。子組件可以直接通過this.xxx的方式來獲取傳遞進來的props參數。 - 限制傳入的 props 型別、預設值、驗證器及是否必填等:使用
物件的方式來定義props,在物件中指定props的名稱,並對其型別、預設值、驗證器和是否必填等進行限制。這樣可以更嚴格地控制傳入的數據,確保組件的行為符合預期。
👉 Vue3 Options API props 基本用法實作連結
以下透過一個範例來說明props的動態綁定基本用法,使用:來進行綁定:
父組件 Vue Template:
<div id="app">
<child-component-a :special-msg="specialMsg"></child-component-a>
<child-component-b :special-msg="specialMsg"></child-component-b>
</div>
父組件 javascript:
// 根組件
const rootComponent = {
data() {
return {
specialMsg: "hello vue!"
};
},
components: { childComponentA, childComponentB }
};
⭐ :special-msg="specialMsg" 中,special-msg是子組件中定義的props名稱,優先使用HTML可解析的 kebab-case(串燒)命名方式,以確保與HTML標準保持一致,並避免可能的瀏覽器解析問題,而 specialMsg是父組件中的變數名稱。
子組件 Vue Template:
<!-- childComponentA 畫面 -->
<template id="childComponentA">
<p>childComponentA title:{{ specialMsg }}</p>
</template>
<!-- childComponentB 畫面 -->
<template id="childComponentB">
<p>childComponentB title:{{ specialMsg }}</p>
</template>
子組件 javascript:
const childComponentA = {
// 陣列定義 props,無型別的定義
props: ["specialMsg"],
template: "#childComponentA"
};
const childComponentB = {
// 物件定義,搭配型別限制
props: {
specialMsg: String
},
template: "#childComponentB"
};
props 單向資料流 - 組件之間的溝通方式
父組件可以透過props將資料傳遞給子組件,然而,子組件無法直接修改由父組件傳遞進來的props。如果子組件需要更改props的值,應該透過 emits事件的方式通知父組件,由父組件來進行變更,而非直接在子組件中修改props。這種單向資料流的設計可以更清楚資料的流向,同時也有助於後續的維護與測試。
如果意圖修改props會跑出錯誤,告訴你props是唯讀的:
props 子組件應用情境說明
如果想要使用props參數在子組件做應用,可以使用下面兩種方式:
- 使用 data 初始化值:
data 函數會在props初始化之後被呼叫一次,因此可以在data中基於props進行初始值的設定。但是其值不會因為後續props改動而連動影響。 - 使用 computed 監聽 props:
computed屬性會在props變更時重新計算,因此可以在computed中進行props的格式化處理或其他計算。
👉 Vue3 Options API props 子組件應用實作連結
子組件 Vue Template:
<template id="child">
<!-- 使用 data 初始化 props 值(初始化執行後不會變更) -->
<p>newCount:{{ newCount }}</p>
<!-- 使用 computed 格式化 props 值(監聽 props 變化執行函數) -->
<p>doubleCount:{{ doubleCount }}</p>
</template>
子組件 javascript:
const childComponent = {
props: ["count"],
template: "#child",
data(){
return{
newCount: this.count + 1
}
},
computed: {
doubleCount() {
return this.count + this.count;
}
}
};
父組件 Vue Template:
<div id="app">
<child-component :count="count"></child-component>
<button @click="add" type="button"> +1</button>
</div>
父組件 javasciprt:
const rootComponent = {
data() {
return {
count: 10
};
},
methods: {
add() {
this.count += 1;
}
},
components: { childComponent }
};
props 傳值類型 - 靜態傳值 vs 動態傳值
當父組件向子組件傳遞值時,如果使用靜態傳值(例如:<test-component isShow="true"></test-component>),isShow參數傳入的值會被視為純字串,因為其未使用v-bind的方式動態綁定。
若需要傳入不同類型的參數值,比如 Number、Boolean、Array、Object 等。應該使用動態傳值的方式,即使用v-bind或縮寫:來動態綁定表達式。這樣可以確保傳入的值保持其原有的數據類型,而不會被轉換為字串。
👉 Vue3 Options API 動態傳值與靜態傳值實作連結
props 多參數傳遞的方式
如果需要將一個物件中的多個屬性傳遞給子組件進行後續的畫面顯示或邏輯處理時,可以使用v-bind 將整個物件直接綁定到子組件。這樣做等於將物件內的所有屬性動態綁定到子組件的對應props上。
流程說明:
- 定義一個
userInfo物件內有多個屬性(name、age) - 將
userInfo全部參數值傳遞給子組件使用
父組件 javascript:
const rootComponent = {
data() {
return {
userInfo: {
name: "Antonio",
age: 25,
}
};
},
components: { childComponent }
};
父組件 Vue Template:
<div id="app">
<!-- 以下兩種達到的效果相同 -->
<child-component v-bind="userInfo"></child-component>
<child-component :name="userInfo.name" :age="userInfo.age"></child-component>
</div>
子組件 Vue Template:
<template id="child">
<div class="card">
<p>姓名:{{ name }}</p>
<p>標題:{{ age }}</p>
</div>
</template>
這邊特別舉一個產品列表陣列包產品資訊物件的案例,讓大家理解當props傳遞物件時,需要注意傳值的陷阱:
👉 Vue3 Options API v-for 搭配 props 傳值陷阱實作連結
流程說明:
- 將產品列表中裡面每一個產品資訊的物件直接透過
v-for綁定動態props給子組件 - 子組件根據這個
props的值當作子組件v-model綁定的初始值(改動子組件的值不應該影響props)
父組件 Vue Template:
<div id="app">
<div class="parent-content">
<h2>我是 parent-content</h2>
<li v-for="product in productList" :key="product.id">
名稱:<input type="text" v-model="product.name">
價格:<input type="text" v-model="product.price">
</li>
</div>
<div class="child-content">
<h2>我是 child-content</h2>
<child-component v-for="product in productList" :key="product.id" :product-info="product"></child-component>
</div>
</div>
⭐ javaScript物件具有引用相同記憶體地址的特性,因此當傳遞給子組件的props不是基礎型別(例如::product-name="product.name"),而是引用型別(如物件或陣列)時,子組件對props的改動會直接影響父組件中的數據。這是因為父子組件之間實際上共享的是對同一物件的引用,而不是物件本身的拷貝。
改正方式可以參考上面多參數傳遞的方式,綁定單一屬性或是透過v-bind一次綁定物件內所有屬性。
👉 Vue3 Options API v-for 搭配 props 傳值手動綁定物件內所有屬性實作連結
👉 Vue3 Options API v-for 搭配 props 傳值透過 v-bind 綁定物件所有屬性實作連結
props 校驗方式 - 檢查所有外來的參數
在 Vue 組件中,props是從父組件傳遞過來的參數,而非子組件本身定義的變數。為了確保後續數據的維護方便,建議對props進行限制和驗證。在開發環境中,如果傳入的props未通過驗證,Vue 會發出警告並說明原因。
限制方式大致可以分成型別限制、必填限制或預設值及驗證函數三種方式,可互相搭配使用:
- 型別限制:通過
javascript的內建構造函數來限制 props 的類型,例如:String、Number、Boolean、Array、Object、Date、Function、Symbol 及 Error 等九種類型。如果傳入的型別是undefined或是null會直接通過型別的檢查。 - 必填限制或預設值(擇一使用)
- 必填(required):指定某個
props必須傳入。 - 預設值(defulat):當父組件沒有傳入
props時,props將使用預設值。判定是否使用預設值的條件在於 props 的值是否為undefined,不論是顯式未傳遞還是隱式未定義,都會觸發default。
- 驗證函式(validator):使用自定義的驗證函數來驗證傳入的
props是否符合特定規則。函數應返回true表示驗證通過,否則返回false。
以下面的案例示範校驗的方式,設定了四個不同型別跟行為限制組合,:
👉 Vue3 Options API props 規則限制實作連結
流程說明:
- 定義父組件變數值:在父組件中,定義了
count、title、userInfo和status四個變數,這些變數將作為子組件的props傳遞,並根據不同的組合進行測試。 - 定義子組件 props 的限制規範:子組件中使用
props接收從根組件傳遞的值,並設定每個props的型別、預設值及驗證規則。 - 測試子組件的四種情形(若未給值會使用 props 定義的預設值):
- 傳入
title及count - 傳入
userInfo及count - 傳入
status(status 會給非合法值及count - 傳入
status(status 給合法值)及count
- 傳入
子組件 javascript:
const childComponent = {
props: {
// 檢查型別是否為 Number 或 String
inputVal: [Number, String],
// 檢查型別是 Number 且為必填
count: {
type: Number,
required: true
},
// 檢查型別是 String,非必填,設定 undefined 情況下的預設值
title: {
type: String,
default: "子組件預設標題"
},
// 檢查型別是 Object,非必填,設定預設物件格式
// params:代表 props 物件,可以讀取其他 props 值(這邊讀取上面定義的 title)
userInfo: {
type: Object,
// params:傳入的 props 物件值
default(params) {
return { name: "預設人員名稱", age: "xxxx", title: params.title };
}
},
// 檢查型別是 String,非必填,設定預設值 big
// 設定驗證器:回傳規則是 true 通過驗證, false 則是未通過
// value:代表傳入的參數值
status: {
type: String,
default: "big",
validator(value) {
return ["big", "medium", "small"].includes(params);
}
}
},
template: "#child"
};
⭐ userInfo物件必須透過工廠函數來定義預設值,不能直接定義。因為如果直接使用物件來設定預設值,會導致所有使用該預設值的組件共享相同的物件引用,從而造成不同組件之間的資料相互影響。使用工廠函數可以確保每個組件實例擁有自己獨立的物件。
子組件 Vue Template
<template id="child">
<div class="card">
<h2>{{ title }}</h2>
<p>Count:{{ count }}</p>
<p>{{ userInfo }}</p>
<p>狀態:{{ status }}</p>
</div>
</template>
父組件 Vue Template:
<div id="app">
<div class="card-group">
<child-component title="父組件傳入的標題" :count="count"></child-component>
<child-component :user-Info="userInfo" :count="count"></child-component>
<child-component status="fail" :count="count"></child-component>
<child-component :status="status" :count="count"></child-component>
</div>
</div>
父組件 javascript:
const rootComponent = {
data() {
return {
count: 0,
userInfo: { name: "Antonio", age: 16 },
status: "small"
};
},
components: { childComponent }
};
⭐ title如果有傳入值,則會影響userInfo中的title屬性;如果title沒有傳入,則使用title 的預設值。
⭐ 即使傳入非合法值,Vue 仍然會接收並使用該值,但在開發環境中會顯示警告訊息。可以幫助開發者在開發過程中更容易發現和維護傳入值的合法性,從而避免潛在的錯誤。
props Boolean 型別規則判定方式
為了貼近原生button屬性的行為,Vue 中的Boolean型別props會遵循以下規則:
- 當
props定義為Boolean型別時,只要在組件標籤上聲明了該prop,即使沒有指定具體的值,它的值也會自動設置為true。如果未在標籤上聲明,則默認為false。 - 當
props同時支援Boolean和其他型別(如 Number),Boolean規則優先適用。也就是說,如果該props被聲明但沒有具體的值,則會設置為true。 - 當
props同時支持Boolean和String型別時,若String型別在Boolean型別前面,且props未給值的時候,Vue 會將其值解析為空字串 "",而非true。
👉 Vue3 Options API props Boolean 判定規則實作連結
結論
- props 是父組件傳遞給子組件的參數:
props可以有兩種定義方式。一種是無限制的參數傳遞,通過陣列來標註允許的props。另一種是使用物件來定義,並透過設定型別和其他屬性來進行傳入參數的驗證與控制。 - props 是單向資料流:
props是父組件傳遞給子組件的單向數據流,子組件不應直接修改props的值。特別是當傳遞的是物件或陣列時,直接修改它們會導致父子組件之間的數據共享,從而引發維護上的困難 - props 校驗規則:允許開發者通過型別檢查、是否必選、以及驗證函數來確保傳入的
props值符合要求。如果props值不合法,Vue 會在開發環境中觸發警告,幫助開發者及時發現問題。 - props Boolean 型別規則:
props中的Boolean屬性遵循類似原生button屬性的行為。當Boolean型別的props被聲明時,會自動被設置為true;若未聲明,則為false。即使props定義中包含多種型別,只要有Boolean,其規則依然適用。但在特殊情況下,當String和Boolean同時定義時,若傳入的值符合String的條件,則該props會被解析為空字串,而不是true。
留言
留言使用 GitHub 帳號登入(Giscus),留言會存在 GitHub Discussions。