更新於 2024/09/30閱讀時間約 1 分鐘

EP22 - 屬性

這一頁假設你已經閱讀了組件基礎。如果你對組件還不熟悉,請先閱讀那部分內容。 Props屬性是 Vue 組件中一個非常重要的概念,用來接收從父組件傳遞過來的數據。使用 props 可以讓組件更加靈活和可重用。慢慢對他有點熟悉了吧!

Prop 定義 - Props Declaration​

Vue 組件需要明確的 prop 定義,這樣 Vue 才能知道應該將傳遞給組件的哪些外部 props 視為透傳屬性 fallthrough attributes(這部分內容將在專門的部分討論)。

在使用 <script setup> 的單文件組件(SFC)中,可以使用 defineProps() 巨集來定義 props:

<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>

在非 <script setup> 的組件中,props 是使用 props 選項來定義的:

export default {
props: ['foo'],
setup(props) {
// setup() 接收的第一個參數就是 props。
console.log(props.foo)
}
}

注意傳遞給 defineProps() 的參數與提供給 props 選項的值相同:這兩種定義方式共享相同的 props 選項 API。

除了使用字符串數組定義 props 外,我們還可以使用物件語法:

// 在 <script setup> 中
defineProps({
title: String,
likes: Number
})
// 在非 <script setup> 中
export default {
props: {
title: String,
likes: Number
}
}

在物件定義語法中,每個屬性的鍵是 prop 的名字,而值應該是預期類型的建構函數。

這不僅能記錄你的組件,還能在其他開發者在瀏覽器控制台中傳遞錯誤類型時給出警告。關於 prop 驗證的更多細節,我們將在本頁面後面討論。

如果你在 <script setup> 中使用 TypeScript,也可以使用純類型註釋來定義 props:

<script setup lang="ts">
defineProps<{
title? : string
likes?: number
}>()
</script>

更多細節: Typing Component Props

可響應的 Props 解構 - Reactive Props Destructure (+3.5)

Vue 的響應系統根據屬性存取來追蹤狀態使用。例如,當你在計算屬性或監聽器中存取 props.foo 時,foo prop 會被追蹤為一個依賴。

例如,給定以下程式碼:

const { foo } = defineProps(['foo'])

watchEffect(() => {
// 在 3.5 之前只執行一次
// 在 3.5 之後當 "foo" prop 變更時重新執行
console.log(foo)
})

在 3.4 及更低版本中,foo 是一個實際的常量,永遠不會變更。在 3.5 及更高版本中,Vue 的編譯器會在同一 <script setup> 區塊中存取從 defineProps 解構的變數時,自動添加 props. 前綴。因此,以上程式碼相當於以下程式碼:

const props = defineProps(['foo'])

watchEffect(() => {
// `foo` 被編譯器轉換為 `props.foo`
console.log(props.foo)
})

此外,你可以使用 JavaScript 的原生預設值語法為 props 宣告預設值。這在使用基於類型的 props 定義時特別有用:

const { foo = 'hello' } = defineProps<{ foo?: string }>()

如果你希望在 IDE 中更明顯地區分解構的 props 和普通變數,Vue 的 VSCode 擴充提供了一個設定,可以啟用解構 props 的內嵌提示。

將解構的 Props 傳入函數

當我們將解構的 prop 傳入函數時,例如:

const { foo } = defineProps(['foo'])

watch(foo, /* ... */)

這將無法如預期運作,因為它相當於 watch(props.foo, ...) ,我們傳遞的是一個值而不是一個響應式資料源給 watch。事實上,Vue 的編譯器會捕捉這種情況並拋出警告。

類似於我們可以使用 watch(() => props.foo, ...) 來監聽普通的 prop,我們也可以通過將解構的 prop 包裝在 getter 中來監聽:

watch(() => foo, /* ... */)

此外,當我們需要將解構的 prop 傳入外部函數並保留響應性時,這是推薦的方法:

useComposable(() => foo)

外部函數可以在需要追蹤提供的 prop 變化時調用 getter(或使用 toValue 進行標準化),例如在計算屬性或監聽器的 getter 中。

Prop 傳遞詳情 - Prop Passing Details

Prop 名稱大小寫

我們使用 camelCase 來宣告較長的 prop 名稱,因為這樣可以避免在使用它們作為屬性鍵時需要加上引號,並且允許我們在模板表達式中直接引用它們,因為它們是有效的 JavaScript 標識符:

defineProps({
greetingMessage: String
})
<span>{{ greetingMessage }}</span>

技術上來說,當傳遞 props 給子組件時(除非在 DOM 模板中),你也可以使用 camelCase。然而,慣例是在所有情況下都使用 kebab-case,以對齊 HTML 屬性:

<MyComponent greeting-message="hello" />

我們盡可能使用 PascalCase 來命名組件標籤,因為這樣可以通過區分 Vue 組件和原生元素來提高模板的可讀性。然而,在傳遞 props 時使用 camelCase 並沒有太大的實際好處,所以我們選擇遵循每種語言的慣例。

靜態 vs. 動態 Props

到目前為止,你已經看到了作為靜態值傳遞的 props,例如:

<BlogPost title="My journey with Vue" />

你也看到了使用 v-bind 或其 : 簡寫來動態賦值的 props,例如:

<!-- 動態賦值變量的值 -->
<BlogPost :title="post.title" />

<!-- 動態賦值複雜表達式的值 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

傳遞不同類型的值

在上面的兩個例子中,我們碰巧傳遞的是字符串值,但可以傳遞給 prop 的值類型不限於此。

數字

<!-- 即使 `42` 是靜態的,我們也需要 v-bind 來告訴 Vue 這是 JavaScript 表達式,而不是字符串。 -->
<BlogPost :likes="42" />

<!-- 動態賦值變量的值。 -->
<BlogPost :likes="post.likes" />

布林值

<!-- 包含沒有值的 prop 將默認為 `true`-->
<BlogPost is-published />

<!-- 即使 `false` 是靜態的,我們也需要 v-bind 來告訴 Vue 這是 JavaScript 表達式,而不是字符串。 -->
<BlogPost :is-published="false" />

<!-- 動態賦值變量的值。 -->
<BlogPost :is-published="post.isPublished" />

陣列

<!-- 即使陣列是靜態的,我們也需要 v-bind 來告訴 Vue 這是 JavaScript 表達式,而不是字符串。 -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- 動態賦值變量的值。 -->
<BlogPost :comment-ids="post.commentIds" />

物件

<!-- 即使物件是靜態的,我們也需要 v-bind 來告訴 Vue 這是 JavaScript 表達式,而不是字符串。 -->
<BlogPost
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
/>

<!-- 動態賦值變量的值。 -->
<BlogPost :author="post.author" />

使用物件綁定多個屬性

如果你想將物件的所有屬性作為 props 傳遞,你可以使用不帶參數的 v-bind(即 v-bind 而不是 :prop-name)。例如,給定一個 post 物件:

const post = {
id: 1,
title: 'My Journey with Vue'
}

以下模板:

<BlogPost v-bind="post" />

將等同於:

<BlogPost :id="post.id" :title="post.title" />

單向數據流 - One-Way Data Flow​

所有 props 形成從父屬性到子屬性的單向綁定:當父屬性更新時,它會向下傳遞到子屬性,但不會反過來。這樣可以防止子組件意外修改父屬性狀態,從而使應用程序的數據流更容易理解。

此外,每當父組件更新時,子組件中的所有 props 都將刷新為最新的值。這意味著你不應該嘗試在子組件中修改 prop。如果你這樣做,Vue 會在控制台中發出警告:

const props = defineProps(['foo'])

// ❌ 警告,props 是只讀的!
props.foo = 'bar'

通常有兩種情況會讓你想要修改 prop:

  1. Prop 用於傳入初始值;子組件希望之後將其用作本地數據屬性。在這種情況下,最好定義一個本地數據屬性,使用 prop 作為其初始值:
    const props = defineProps(['initialCounter'])

    // counter 只使用 props.initialCounter 作為初始值;
    // 它與未來的 prop 更新斷開連接。
    const counter = ref(props.initialCounter)
  2. Prop 作為需要轉換的原始值傳入。在這種情況下,最好定義一個計算屬性,使用 prop 的值:
const props = defineProps(['size'])

// 當 prop 變更時自動更新的計算屬性
const normalizedSize = computed(() => props.size.trim().toLowerCase())

修改物件 / 陣列 Props

當物件和陣列作為 props 傳遞時,雖然子組件不能修改 prop 綁定,但它可以修改物件或陣列的嵌套屬性。這是因為在 JavaScript 中,物件和陣列是通過引用傳遞的,而 Vue 阻止這種修改代價過高。

這種修改的主要缺點是,它允許子組件以父組件不明顯的方式影響父狀態,可能使未來的數據流變得更難理解。作為最佳實踐,你應該避免這種修改,除非父子組件是設計上緊密耦合的。在大多數情況下,子組件應該發出一個事件,讓父組件進行修改。

Prop 驗證 - Prop Validation​

組件可以為它們的 props 指定需求,例如你已經看到的類型。如果不符合需求,Vue 會在瀏覽器的 JavaScript 控制台中發出警告。這在開發要被其他人使用的組件時尤其有用。

要指定 prop 驗證,你可以向 defineProps() 巨集提供一個帶有驗證需求的物件,而不是一個字符串數組。例如:

defineProps({
// 基本類型檢查
// (`null` 和 `undefined` 值將允許任何類型)
propA: Number,
// 多種可能的類型
propB: [String, Number],
// 必需的字符串
propC: {
type: String,
required: true
},
// 必需但可為空的字符串
propD: {
type: [String, null],
required: true
},
// 有默認值的數字
propE: {
type: Number,
default: 100
},
// 有默認值的物件
propF: {
type: Object,
// 物件或數組的默認值必須從工廠函數返回。該函數接收組件接收到的原始 props 作為參數。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定義驗證函數
// 在 3.4+ 中,完整的 props 作為第二個參數傳遞
propG: {
validator(value, props) {
// 值必須匹配這些字符串之一
return ['success', 'warning', 'danger'].includes(value)
}
},
// 有默認值的函數
propH: {
type: Function,
// 不像物件或數組默認值,這不是工廠函數——這是一個用作默認值的函數
default() {
return 'Default function'
}
}
})

提示

defineProps() 參數內的代碼無法訪問 <script setup> 中聲明的其他變數,因為整個表達式在編譯時被移動到外部函數作用域。

額外詳情:

  • 所有 props 默認都是可選的,除非指定了 required: true
  • 除了 Boolean 之外,缺失的可選 prop 將具有 undefined 值。
  • 缺失的 Boolean props 將被轉換為 false。你可以通過為其設置默認值來更改這一點——即:default: undefined 使其表現為非 Boolean prop。
  • 如果指定了默認值,當解析出的 prop 值為 undefined 時將使用它——這包括 prop 缺失或傳遞了明確的 undefined 值。
  • 當 prop 驗證失敗時,Vue 將產生控制台警告(如果使用開發版本)。
  • 如果使用基於類型的 props 聲明,Vue 將盡力將類型註釋編譯為等效的運行時 prop 聲明。例如,defineProps<{ msg: string }> 將編譯為 { msg: { type: String, required: true }}

運行時類型檢查

類型可以是以下本地構造函數之一:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
  • Error

此外,類型還可以是一個自定義類或構造函數,並且將使用 instanceof 檢查進行斷言。例如,給定以下類:

class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}

你可以將其用作 prop 的類型:

defineProps({
author: Person
})

Vue 將使用 instanceof Person 來驗證 author prop 的值是否確實是 Person 類的實例。

可為空的類型

如果類型是必需但可為空的,你可以使用包含 null 的數組語法:

defineProps({
id: {
type: [String, null],
required: true
}
})

請注意,如果類型只是 null 而不使用數組語法,它將允許任何類型。

布林值轉換 - Boolean Casting​

具有布林(Boolean)類型的 props 具有特殊的轉換規則,以模擬本地布林屬性的行為。假設有一個具有以下聲明的 <MyComponent>

defineProps({
disabled: Boolean
})

組件可以這樣使用:

<!-- 等同於傳遞 :disabled="true" -->
<MyComponent disabled />

<!-- 等同於傳遞 :disabled="false" -->
<MyComponent />

當 prop 被聲明為允許多種類型時,也會應用布林值的轉換規則。但是,當同時允許字符串(String)和布林值時,有一個邊界情況——只有當布林值出現在字符串之前時才會應用布林值的轉換規則:

// disabled 會被轉換為 true
defineProps({
disabled: [Boolean, Number]
})

// disabled 會被轉換為 true
defineProps({
disabled: [Boolean, String]
})

// disabled 會被轉換為 true
defineProps({
disabled: [Number, Boolean]
})

// disabled 會被解析為空字符串 (disabled="")
defineProps({
disabled: [String, Boolean]
})
老實說!這篇文章我是邊看邊睡覺~哈
理解力太差 www ~~ 小睡一會!再回來看看應該會比較懂?
懂原理,但是細節有點多的時候~真的很想睡www~
分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.