跳轉到內容

屬性

本頁面假設您已經閱讀了元件基礎。如果您是元件的新手,請先閱讀。

屬性宣告

Vue 元件需要顯式宣告屬性,這樣 Vue 才知道哪些外部傳遞給元件的屬性應該被視為透傳屬性(將在其專屬部分中討論)。

<script setup> 模板中使用的 SFC,可以使用 defineProps() 宏宣告屬性

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

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

在非 <script setup> 元件中,使用 props 選項宣告屬性

js
export default {
  props: ['foo'],
  setup(props) {
    // setup() receives props as the first argument.
    console.log(props.foo)
  }
}

注意傳遞給 defineProps() 的引數與傳遞給 props 選項的值相同:兩種宣告方式共享相同的屬性選項 API。

使用 props 選項宣告屬性

js
export default {
  props: ['foo'],
  created() {
    // props are exposed on `this`
    console.log(this.foo)
  }
}

除了使用字串陣列宣告屬性外,我們還可以使用物件語法

js
export default {
  props: {
    title: String,
    likes: Number
  }
}
js
// in <script setup>
defineProps({
  title: String,
  likes: Number
})
js
// in non-<script setup>
export default {
  props: {
    title: String,
    likes: Number
  }
}

在物件宣告語法中,每個屬性的鍵是屬性名,而值應該是預期型別的建構函式。

這不僅會記錄您的元件,還會在瀏覽器控制檯中警告其他使用您的元件的開發者,如果他們傳遞了錯誤型別。我們將在本頁面的下方進一步討論關於屬性驗證的更多細節。

如果您正在使用 TypeScript 與 <script setup>,還可以使用純型別註解宣告屬性

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

更多詳情:為元件屬性進行型別定義

響應式屬性解構

Vue 的反應性系統根據屬性訪問跟蹤狀態使用情況。例如,當您在計算屬性或觀察者中訪問 props.foo 時,foo 屬性被跟蹤為依賴項。

所以,給定以下程式碼

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

watchEffect(() => {
  // runs only once before 3.5
  // re-runs when the "foo" prop changes in 3.5+
  console.log(foo)
})

在3.4版本及以下,foo是一個實際的常量,將永遠不會改變。從3.5版本開始,Vue的編譯器會在同一<script setup>塊中訪問從defineProps解構的變數時自動新增props.。因此,上述程式碼等同於以下程式碼

js
const props = defineProps(['foo'])

watchEffect(() => {
  // `foo` transformed to `props.foo` by the compiler
  console.log(props.foo)
})

此外,您可以使用JavaScript的原生預設值語法來宣告props的預設值。這在使用基於型別的props宣告時尤其有用。

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

如果您希望在使用IDE時在解構props和普通變數之間有更多的視覺區分,Vue的VSCode擴充套件提供了啟用解構props的inlay-hints的設定。

將解構props傳遞給函式

當我們將解構prop傳遞給一個函式時,例如

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

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

這不會按預期工作,因為它等同於watch(props.foo, ...) - 我們傳遞的是一個值而不是reactive資料來源到watch。實際上,Vue的編譯器會捕獲此類情況並丟擲警告。

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

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

此外,當我們需要在傳遞解構prop到外部函式的同時保留reactivity時,這是推薦的方法。

js
useComposable(() => foo)

外部函式可以在需要跟蹤提供的prop變化時呼叫getter(或使用toValue進行標準化)。

Prop傳遞詳情

Prop名稱大小寫

我們使用camelCase來宣告長prop名稱,因為這樣可以避免在使用它們作為屬性鍵時使用引號,並允許我們在模板表示式中直接引用它們,因為它們是有效的JavaScript識別符號。

js
defineProps({
  greetingMessage: String
})
js
export default {
  props: {
    greetingMessage: String
  }
}
template
<span>{{ greetingMessage }}</span>

技術上,在將props傳遞給子元件時也可以使用camelCase(除了在in-DOM模板中)。然而,慣例是在所有情況下都使用kebab-case,以與HTML屬性保持一致。

template
<MyComponent greeting-message="hello" />

在可能的情況下,我們使用PascalCase作為元件標籤,因為它可以透過區分Vue元件和原生元素來提高模板的可讀性。然而,在傳遞props時使用camelCase並沒有太大的實際好處,因此我們選擇遵循每種語言的約定。

靜態與動態props

到目前為止,您已經看到了像下面這樣的靜態值傳遞的props

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

您也看到了使用v-bind或其:快捷方式動態分配的props,例如

template
<!-- Dynamically assign the value of a variable -->
<BlogPost :title="post.title" />

<!-- Dynamically assign the value of a complex expression -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

傳遞不同型別值

在上面的兩個示例中,我們傳遞了字串值,但可以傳遞任何型別的值給prop。

數字

template
<!-- Even though `42` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.       -->
<BlogPost :likes="42" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :likes="post.likes" />

布林值

template
<!-- Including the prop with no value will imply `true`. -->
<BlogPost is-published />

<!-- Even though `false` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.          -->
<BlogPost :is-published="false" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :is-published="post.isPublished" />

陣列

template
<!-- Even though the array is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.            -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :comment-ids="post.commentIds" />

物件

template
<!-- Even though the object is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.             -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
 />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :author="post.author" />

使用物件繫結多個屬性

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

js
export default {
  data() {
    return {
      post: {
        id: 1,
        title: 'My Journey with Vue'
      }
    }
  }
}
js
const post = {
  id: 1,
  title: 'My Journey with Vue'
}

以下模板

template
<BlogPost v-bind="post" />

將等同於

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

單向資料流

所有props在子元件屬性和父元件屬性之間形成單向繫結:當父元件屬性更新時,它會流向子元件,但不會反過來。這可以防止子元件意外地修改父元件的狀態,這可能會使您的應用程式的資料流更難以理解。

此外,每當父元件更新時,子元件中的所有props都會重新整理為最新值。這意味著您不應該在子元件內部嘗試修改prop。如果您這樣做,Vue將在控制檯警告您

js
const props = defineProps(['foo'])

// ❌ warning, props are readonly!
props.foo = 'bar'
js
export default {
  props: ['foo'],
  created() {
    // ❌ warning, props are readonly!
    this.foo = 'bar'
  }
}

通常有兩種情況下可能會誘使您修改prop

  1. 該prop用於傳遞初始值;子元件之後想將其用作本地資料屬性。在這種情況下,最好定義一個本地資料屬性,該屬性使用prop作為其初始值

    js
    const props = defineProps(['initialCounter'])
    
    // counter only uses props.initialCounter as the initial value;
    // it is disconnected from future prop updates.
    const counter = ref(props.initialCounter)
    js
    export default {
      props: ['initialCounter'],
      data() {
        return {
          // counter only uses this.initialCounter as the initial value;
          // it is disconnected from future prop updates.
          counter: this.initialCounter
        }
      }
    }
  2. 該prop是一個需要轉換的原始值。在這種情況下,最好定義一個使用prop值的計算屬性

    js
    const props = defineProps(['size'])
    
    // computed property that auto-updates when the prop changes
    const normalizedSize = computed(() => props.size.trim().toLowerCase())
    js
    export default {
      props: ['size'],
      computed: {
        // computed property that auto-updates when the prop changes
        normalizedSize() {
          return this.size.trim().toLowerCase()
        }
      }
    }

修改物件/陣列Props

當物件和陣列作為props傳遞時,儘管子元件不能修改prop繫結,但它將能夠修改物件或陣列的巢狀屬性。這是因為JavaScript中的物件和陣列是透過引用傳遞的,Vue阻止這種修改是不合理的昂貴。

這種修改的主要缺點是它允許子元件以父元件不明顯的方式影響父狀態,這可能會使未來對資料流的推理更加困難。作為一個最佳實踐,除非父元件和子元件在設計上緊密耦合,否則應避免此類修改。在大多數情況下,子元件應發出事件,讓父元件執行修改。

Prop驗證

元件可以指定其props的要求,例如您已經看到的型別。如果未滿足要求,Vue將在瀏覽器JavaScript控制檯中警告您。這對於開發打算供他人使用的元件非常有用。

要指定prop驗證,您可以提供一個包含驗證要求的物件到defineProps()props選項,而不是字串陣列。例如

js
defineProps({
  // Basic type check
  //  (`null` and `undefined` values will allow any type)
  propA: Number,
  // Multiple possible types
  propB: [String, Number],
  // Required string
  propC: {
    type: String,
    required: true
  },
  // Required but nullable string
  propD: {
    type: [String, null],
    required: true
  },
  // Number with a default value
  propE: {
    type: Number,
    default: 100
  },
  // Object with a default value
  propF: {
    type: Object,
    // Object or array defaults must be returned from
    // a factory function. The function receives the raw
    // props received by the component as the argument.
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // Custom validator function
  // full props passed as 2nd argument in 3.4+
  propG: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // Function with a default value
  propH: {
    type: Function,
    // Unlike object or array default, this is not a factory
    // function - this is a function to serve as a default value
    default() {
      return 'Default function'
    }
  }
})

提示

defineProps()引數內的程式碼不能訪問在<script setup>中宣告的其他變數,因為在編譯時整個表示式被移動到外部函式作用域。

js
export default {
  props: {
    // Basic type check
    //  (`null` and `undefined` values will allow any type)
    propA: Number,
    // Multiple possible types
    propB: [String, Number],
    // Required string
    propC: {
      type: String,
      required: true
    },
    // Required but nullable string
    propD: {
      type: [String, null],
      required: true
    },
    // Number with a default value
    propE: {
      type: Number,
      default: 100
    },
    // Object with a default value
    propF: {
      type: Object,
      // Object or array defaults must be returned from
      // a factory function. The function receives the raw
      // props received by the component as the argument.
      default(rawProps) {
        return { message: 'hello' }
      }
    },
    // Custom validator function
    // full props passed as 2nd argument in 3.4+
    propG: {
      validator(value, props) {
        // The value must match one of these strings
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // Function with a default value
    propH: {
      type: Function,
      // Unlike object or array default, this is not a factory
      // function - this is a function to serve as a default value
      default() {
        return 'Default function'
      }
    }
  }
}

其他詳細資訊

  • 預設情況下,所有prop都是可選的,除非指定了required: true

  • 除了Boolean之外,缺失的可選屬性將具有undefined的值。

  • 缺失的Boolean屬性將被轉換為false。您可以透過為其設定預設值來更改此行為,即:default: undefined,以使其作為非布林屬性。

  • 如果指定了預設值,則在解析的屬性值為undefined時將使用該預設值 - 這包括屬性缺失或顯式傳遞undefined值的情況。

當屬性驗證失敗時,Vue將在開發構建中生成控制檯警告。

如果使用基於型別的屬性宣告 ,Vue將盡可能將型別註解編譯成等效的執行時屬性宣告。例如,defineProps<{ msg: string }>將被編譯成{ msg: { type: String, required: true }}

注意

請注意,屬性驗證是在建立元件例項之前進行的,因此例項屬性(例如datacomputed等)不會在defaultvalidator函式內部可用。

執行時型別檢查

type可以是以下原生建構函式之一

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

此外,type也可以是自定義類或建構函式,並且將透過instanceof檢查進行斷言。例如,給定以下類

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

您可以使用它作為屬性的型別

js
defineProps({
  author: Person
})
js
export default {
  props: {
    author: Person
  }
}

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

可空型別

如果型別是必需的但可空的,可以使用包含null的陣列語法

js
defineProps({
  id: {
    type: [String, null],
    required: true
  }
})
js
export default {
  props: {
    id: {
      type: [String, null],
      required: true
    }
  }
}

請注意,如果type僅為null而不使用陣列語法,它將允許任何型別。

布林轉換

具有Boolean型別的屬性具有特殊的轉換規則,以模仿原生布爾屬性的行為。給定以下宣告的<MyComponent>

js
defineProps({
  disabled: Boolean
})
js
export default {
  props: {
    disabled: Boolean
  }
}

元件可以使用如下方式使用

template
<!-- equivalent of passing :disabled="true" -->
<MyComponent disabled />

<!-- equivalent of passing :disabled="false" -->
<MyComponent />

當一個屬性宣告為允許多個型別時,Boolean的轉換規則也將適用。但是,當同時允許StringBoolean時,存在一個邊緣情況 - 只有當布林值出現在字串之前時,布林轉換規則才適用

js
// disabled will be casted to true
defineProps({
  disabled: [Boolean, Number]
})

// disabled will be casted to true
defineProps({
  disabled: [Boolean, String]
})

// disabled will be casted to true
defineProps({
  disabled: [Number, Boolean]
})

// disabled will be parsed as an empty string (disabled="")
defineProps({
  disabled: [String, Boolean]
})
js
// disabled will be casted to true
export default {
  props: {
    disabled: [Boolean, Number]
  }
}

// disabled will be casted to true
export default {
  props: {
    disabled: [Boolean, String]
  }
}

// disabled will be casted to true
export default {
  props: {
    disabled: [Number, Boolean]
  }
}

// disabled will be parsed as an empty string (disabled="")
export default {
  props: {
    disabled: [String, Boolean]
  }
}
屬性已載入