VUE元件--props資料捕手
在談 props
之前,先來說說子元件的資料來源:
- 自己的資料:也就是子元件裡面自定義的資料。
- 父元件
props
進子元件的資料:這是子元件跟父元件借資料來用,不屬於子元件所有。 - Store 裡面大家公用的資料: 例如放在 pinia 與 vuex 裡面的資料。
如果是要同層級的元件間互相傳遞資料,或是跨好幾個層級的元件要傳遞資料,並不適合使用 props。
props 是子元件的資料接收器
父元件如果要傳送資料給子元件必須透過 props
,而且必須遵守單向資料流的規則 ,也就是子元件接收到父元件的資料後,不能去修改那個資料,因為那份資料可能是給很多個子元件共用的。
內層子元件定義 props 接收
以 <script setup>
來舉例,必須在子元件先定義 defineProps
。
<script setup>
defineProps(['title','message'])
</script>
<template>
{{title}}
</template>
或是用一個變數來定義 defineProps
。
<script setup>
const prop=defineProps(['title','message'])
console.log(props.title)
</script>
<template>
{{props.title}}
</template>
可以在陣列中放入多個接收父元件資料的 props
聲明,Vue 才能知道父元件傳入子元件的哪些是 props。而defineProps
預設會返回一個帶有所 props
屬性的物件。
一個元件可以有多個 props,預設都可以接收任何型別的值,包含字串、數值、布林值、陣列、物件及函數。
子元件內的 props 命名要使用小駝峰形式。
defineProps(['ironMan'])
父元件中用 v-bind 綁定props,則使用 kebab-case
<MyComponent iron-man="tony" />
props 接收動態資料
而在父層中要傳送資料給子元件,可以這樣做:
<template>
<ChildComp :message="catcher"></ChildComp>
</tmplate>
在上面的範例中,使用 v-bind
綁定內層子元件的 props
,後面則是放外層父元件要傳送的資料,口訣為「前內後外」。
假設子元件的內層 defineProps 如此聲明:
<script setup>
import { ref } from 'vue';
defineProps(['msg']);
const count = ref(0);
function addNum(n) {
count.value += n;
}
</script>
<template>
<h2>{{ msg }}</h2>
<button @click="addNum(1)">ADD</button>
Count: {{ count }}
</template>
父元件的資料如此定義:
<script setup>
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
const name = ref('第二個HelloWorld');
</script>
外層父元件可以如此傳資料給子元件:
<HelloWorld :msg="name" />
// msg 是內層的 props,name 是外層的變數
props 接收靜態資料
如果是靜態資料,依樣是遵守「前內後外」的原則,但是不用 v-bind
綁定。
<ChildComp :message="我是一個要傳入子元件的靜態資料"></ChildComp>
外層父元件可以如此傳資料給子元件:
<HelloWorld :msg="第一個HelloWorld" />
// msg 是內層的 props,name 是靜態的字串
props 與 v-for 的結合
如果父元件要傳給子元件的資料是陣列的形式,而且每個子元件要呈現的資料隨陣列的 index 不同,可以搭配 v-for
來傳遞多筆資料。
父元件中的資料型態:
const msg = ref([
{id: 1,text: '我是第一個元件'},
{id: 2,text: '我是第二個元件'},
{id: 2,text: '我是第三個元件'},
])
在子元件中定義 defineProps
:
<script setup>
defineProps(['message'])
</script>
<template>
{{message}}
</template>
在父元件中使用 v-for
渲染多個子元件
<ChildComp v-for="item in msg" :message="item.text" :key="item.id"></ChildComp>
換言之,假設要在父元件中放入多個子元件,而且子元件分別要呈現不同資料,則資料可以設計成陣列物件的形式,用 v-for
來跑子元件。
單向數據流
前面有提到 props 由父元件往子元件傳遞資料是單向數據流,當父元件的資料更新的時候,所有子元件的 props 會更新到最新值,所以如果你在某個元件更改了 props 的資料,則會破壞子元件間的獨立狀態,Vue 會在控制台中示警。
這意味著你不能也不該在子元件中更改 props 的資料。
但是如果你要在子元件中操作運算 props 進來的資料,可以這樣做:
props 作為初始值,指定給一個新的變數
傳入的 props 要變成某個物件的屬性,再指給一個新的變數,這樣新的變數就可以自由操作運算,而不會去改到父層的資料。
<script setup>
import { ref } from 'vue';
const prop = defineProps(['msg']);
// 要把 msg 變成一個物件的屬性,才能運作
const newMsg = ref(prop.msg);
</script>
<template>
<h2>msg {{ msg }}</h2>
<h3>newMsg {{ newMsg }}</h3>
<input v-model="newMsg" />
</template>
將傳入的 props 轉換,例如把他指定給一個 computed 去產生一個新的資料。
props 驗證
前面範例的 props 大多放在一個陣列之中,但是實務上,放入物件是比較好的做法,這樣做的同時可以把驗證的條件帶入 props 的屬性中,例如指定型別、設定預設值及是否為必須。
以下說明,取自 Vue 的官網:
defineProps({
// 基礎型別檢查
// (帶入 `null` 和 `undefined` 會跳過任何型別檢查)
propA: Number,
// 檢查多種型別
propB: [String, Number],
// require 必傳,且為 String 型別
propC: {
type: String,
required: true
},
// Number 型別,default 為預設值
propD: {
type: Number,
default: 100
},
// 物件型別的預設值
propE: {
type: Object,
// 物件或陣列的預設值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自訂義行型別叫驗函數
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函數型別的預設值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})