Vue Props 屬性傳遞
Props 是 Vue 元件之間傳遞資料的主要方式。父元件可以透過 props 將資料傳遞給子元件,實現元件之間的通訊。
定義 Props
在 <script setup> 中,使用 defineProps() 來定義元件接收的 props:
陣列語法
最簡單的方式是使用字串陣列:
<script setup>
const props = defineProps(['title', 'author', 'likes'])
</script>
<template>
<h2>{{ title }}</h2>
<p>作者:{{ author }}</p>
<p>按讚數:{{ likes }}</p>
</template>
物件語法(推薦)
使用物件語法可以為每個 prop 指定型別:
<script setup>
const props = defineProps({
title: String,
author: String,
likes: Number,
isPublished: Boolean
})
</script>
傳遞 Props
靜態 Props
<template>
<BlogPost title="Vue 入門教學" author="小明" />
</template>
動態 Props
使用 v-bind(或簡寫 :)傳遞動態值:
<script setup>
import { ref } from 'vue'
const post = ref({
title: 'Vue 入門教學',
author: '小明',
likes: 42
})
</script>
<template>
<BlogPost
:title="post.title"
:author="post.author"
:likes="post.likes"
/>
</template>
傳遞不同型別的值
<script setup>
import { ref } from 'vue'
const count = ref(100)
const isActive = ref(true)
const tags = ref(['vue', 'javascript'])
const author = ref({ name: '小明', email: 'ming@example.com' })
</script>
<template>
<!-- 傳遞數字 -->
<MyComponent :count="count" />
<MyComponent :count="42" />
<!-- 傳遞布林值 -->
<MyComponent :is-active="isActive" />
<MyComponent is-active /> <!-- 等同於 :is-active="true" -->
<MyComponent :is-active="false" />
<!-- 傳遞陣列 -->
<MyComponent :tags="tags" />
<MyComponent :tags="['vue', 'react']" />
<!-- 傳遞物件 -->
<MyComponent :author="author" />
<MyComponent :author="{ name: '小華' }" />
</template>
一次傳遞所有屬性
如果你想把物件的所有屬性當作 props 傳入,可以使用不帶參數的 v-bind:
<script setup>
import { reactive } from 'vue'
const post = reactive({
id: 1,
title: 'Vue 入門',
author: '小明'
})
</script>
<template>
<!-- 等同於 <BlogPost :id="post.id" :title="post.title" :author="post.author" /> -->
<BlogPost v-bind="post" />
</template>
Props 驗證
使用物件語法可以對 props 進行更詳細的驗證:
<script setup>
const props = defineProps({
// 基本型別檢查(null 和 undefined 會通過任何型別驗證)
propA: Number,
// 多種可能的型別
propB: [String, Number],
// 必填的字串
propC: {
type: String,
required: true
},
// 有預設值的數字
propD: {
type: Number,
default: 100
},
// 有預設值的物件
propE: {
type: Object,
// 物件或陣列的預設值必須從工廠函式返回
default() {
return { message: 'hello' }
}
},
// 自訂驗證函式
propF: {
validator(value) {
// 值必須是以下字串之一
return ['success', 'warning', 'danger'].includes(value)
}
},
// 有預設值的函式
propG: {
type: Function,
// 不像物件或陣列,這不是工廠函式
// 這就是預設值本身
default() {
return 'Default function'
}
}
})
</script>
型別檢查
type 可以是以下原生建構函式:
StringNumberBooleanArrayObjectDateFunctionSymbol
也可以是自訂的類別或建構函式:
<script setup>
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
const props = defineProps({
author: Person
})
</script>
單向資料流
Props 形成了單向資料流:父元件的更新會向下流動到子元件,但反過來則不行。這是為了防止子元件意外修改父元件的狀態。
不要在子元件中直接修改 props! Vue 會在控制台發出警告。
想要修改 prop 的情況
情況一:prop 用來傳遞初始值,之後子元件想要將其作為本地資料使用
<script setup>
import { ref } from 'vue'
const props = defineProps(['initialCounter'])
// 用 prop 的值初始化一個本地 ref
const counter = ref(props.initialCounter)
// 之後修改 counter 不會影響父元件
</script>
情況二:需要對 prop 進行轉換
<script setup>
import { computed } from 'vue'
const props = defineProps(['size'])
// 使用 computed 進行轉換
const normalizedSize = computed(() => props.size.trim().toLowerCase())
</script>
Props 命名慣例
在元件定義中使用 camelCase
<!-- 子元件 -->
<script setup>
defineProps({
greetingMessage: String
})
</script>
在模板中使用 kebab-case
<!-- 父元件 -->
<template>
<!-- 在 HTML 中使用 kebab-case -->
<MyComponent greeting-message="hello" />
</template>
Vue 會自動轉換,所以 greetingMessage 和 greeting-message 是對應的。
使用 TypeScript 定義 Props
如果你使用 TypeScript,可以使用純型別聲明:
<script setup lang="ts">
// 使用型別
interface Props {
title: string
author?: string // 可選
likes?: number
}
// 方式一:使用泛型
const props = defineProps<Props>()
// 方式二:使用泛型並指定預設值
const props = withDefaults(defineProps<Props>(), {
author: '匿名',
likes: 0
})
</script>
更多 TypeScript 的用法請參考 Vue + TypeScript。
Boolean Props 的特殊行為
布林型別的 props 有特殊的轉換規則:
<!-- 子元件 -->
<script setup>
defineProps({
disabled: Boolean
})
</script>
<!-- 父元件 -->
<template>
<!-- 等同於 :disabled="true" -->
<MyComponent disabled />
<!-- 等同於 :disabled="false" -->
<MyComponent />
</template>
當 prop 可以是多種型別時:
<script setup>
// disabled prop 可以接受空字串或布林值
defineProps({
disabled: [Boolean, String]
})
</script>
<template>
<!-- 等同於 :disabled="true" -->
<MyComponent disabled />
<!-- 等同於 :disabled="''" (空字串) -->
<MyComponent disabled="" />
</template>
存取 Props
在 <script setup> 中,defineProps() 返回的物件可以用來存取 props:
<script setup>
const props = defineProps(['title'])
// 在 JavaScript 中使用
console.log(props.title)
</script>
<template>
<!-- 在模板中可以直接使用,不需要 props 前綴 -->
<h1>{{ title }}</h1>
</template>
實際範例
文章卡片元件
<!-- ArticleCard.vue -->
<script setup>
defineProps({
title: {
type: String,
required: true
},
excerpt: {
type: String,
default: ''
},
author: {
type: Object,
default: () => ({ name: '匿名' })
},
publishedAt: {
type: Date,
default: () => new Date()
},
tags: {
type: Array,
default: () => []
},
featured: {
type: Boolean,
default: false
}
})
</script>
<template>
<article :class="{ featured }">
<h2>{{ title }}</h2>
<p class="excerpt">{{ excerpt }}</p>
<div class="meta">
<span class="author">{{ author.name }}</span>
<time :datetime="publishedAt.toISOString()">
{{ publishedAt.toLocaleDateString() }}
</time>
</div>
<div class="tags" v-if="tags.length">
<span v-for="tag in tags" :key="tag" class="tag">{{ tag }}</span>
</div>
</article>
</template>
<style scoped>
article {
border: 1px solid #ddd;
padding: 20px;
border-radius: 8px;
}
article.featured {
border-color: #42b883;
background-color: #f0fff4;
}
.excerpt {
color: #666;
}
.meta {
font-size: 14px;
color: #999;
}
.tag {
background-color: #eee;
padding: 2px 8px;
border-radius: 4px;
margin-right: 5px;
}
</style>
使用這個元件:
<script setup>
import ArticleCard from './ArticleCard.vue'
const article = {
title: 'Vue 3 完整教學',
excerpt: '從基礎到進階,一步步學會 Vue 3...',
author: { name: '小明', email: 'ming@example.com' },
publishedAt: new Date('2024-01-15'),
tags: ['Vue', 'JavaScript', '前端'],
featured: true
}
</script>
<template>
<ArticleCard
:title="article.title"
:excerpt="article.excerpt"
:author="article.author"
:published-at="article.publishedAt"
:tags="article.tags"
featured
/>
<!-- 或使用 v-bind 一次傳入 -->
<ArticleCard v-bind="article" />
</template>