پراپها
در این صفحه فرض شده که شما از قبل مبانی کامپوننتها را مطالعه کرده اید. اگر با کامپوننتها آشنایی ندارید، ابتدا آن را بخوانید.
تعریف کردن پراپها
کامپوننتهای Vue نیاز به تعریف پراپها بصورت مشخص دارند تا Vue بداند کدام یک از پراپهایی که از خارج کامپوننت به آن پاس داده شده را بصورت fallthrought attributes (که در بخش مخصوص خودش توضیح داده شده) در نظر بگیرد.
هنگام استفاده از SFCها در <script setup>
، پراپها با استفاده از ماکروی defineProps()
میتوانند تعریف شوند:
vue
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
در کامپوننتهایی که از <script setup>
استفاده نمیشود، پراپ ها میتوانند بصورت یک آپشن با نام props
تعریف شوند:
js
export default {
props: ['foo'],
setup(props) {
// .پراپها هستند setup() اولین آرگومان دریافتی در
console.log(props.foo)
}
}
توجه داشته باشید آرگومانی که به defineProps()
پاس داده میشود همان مقداری ارائه شده به آپشن props
است. همان پراپهای options API بین دو روش تعریف پراپ مشترک هستند.
پراپها علاوه بر این که میتوانند بصورت آرایه تعریف شوند، قابلیت تعریف شدن بصورت آبجکت را هم دارند:
js
// <script setup> در
defineProps({
title: String,
likes: Number
})
js
// <script setup> بجز
export default {
props: {
title: String,
likes: Number
}
}
برای هر پراپرتی در سینتکس تعریف کردن پراپ بصورت آبجکت، کلید ( key ) اسم پراپ خواهد بود، در صورتی که مقدار آن باید بصورت یک تابع سازنده از تایپ مقدار آن باشد. (در مثال بالا کلید title
نام پراپ و جنس آن String
هست)
این فقط مستندات و اطلاعات کامپوننت شما نیست، اگر دیگر توسعه دهندگان از تایپهایی بجز آن تایپی که در کامپوننت تعریف شده استفاده کنند، در کنسول هشداری در ارتباط با تایپ اشتباه مشاهده میشود. درباره جزئیات اعتبارسنجی پراپ ها (Props Validation) در ادامه توضیح داده شده است.
اگر همراه با <script setup>
از Typescript استفاده میکنید، میتوانید با استفاده از pure type annotations پراپها را تعریف کنید:
vue
<script setup lang="ts">
defineProps<{
title?: string
likes?: number
}>()
</script>
اطلاعات بیشتر: Typing Component Props
تجزیه پراپهای reactive
سیستم واکنشپذیری Vue، استفاده از state را بر اساس دسترسی به پراپرتیها دنبال میکند. به عنوان مثال، زمانی که شما به props.foo
در یک getter محاسباتی یا یک watcher دسترسی پیدا میکنید، پراپرتی foo
بهعنوان یک وابستگی ردیابی میشود.
برای مثال، با توجه به کد زیر:
js
const { foo } = defineProps(['foo'])
watchEffect(() => {
// قبل از نسخه 3.5 فقط یکبار اجرا میشود
// دوباره اجرا میشود "foo" در نسخه 3.5+ هنگام تغییر پراپ
console.log(foo)
})
در نسخه 3.4 و پایینتر، foo
یک ثابت واقعی است و هرگز تغییر نمیکند. اما در نسخه 3.5 و بالاتر، کامپایلر Vue بهطور خودکار props.
را قبل از دسترسی به متغیرهایی که در یک بلوک کد <script setup>
از defineProps
تجزیه شدهاند، اضافه میکند. بنابراین کد بالا معادل کد زیر خواهد بود:
js
const props = defineProps(['foo'])
watchEffect(() => {
// تبدیل شده است `props.foo` توسط کامپایلر به `foo`
console.log(props.foo)
})
علاوه بر این، شما میتوانید از سینتکس مقدار پیشفرض جاوا اسکریپت برای اعلام مقادیر پیشفرض برای props استفاده کنید. این ویژگی بهویژه هنگام استفاده از تعریف props بر اساس تایپ بسیار مفید است:
ts
const { foo = 'hello' } = defineProps<{ foo?: string }>()
اگر ترجیح میدهید که تفاوت بین props تجزیهشده و متغیرهای معمولی در IDE خود واضحتر باشد، افزونه VSCode در vue تنظیماتی برای فعال کردن نکات درونخطی (inlay-hints) برای props تجزیهشده ارائه میدهد.
پاس دادن Props تجزیهشده به توابع
وقتی ما یک prop تجزیهشده را به یک تابع ارسال میکنیم، مثلاً:
js
const { foo } = defineProps(['foo'])
watch(foo, /* ... */)
این بهدرستی کار نخواهد کرد، زیرا معادل watch(props.foo, ...)
است - ما در واقع بهجای یک منبع داده reactive، یک value را به watch
ارسال میکنیم. در واقع، کامپایلر Vue چنین مواردی را تشخیص میدهد و یک هشدار صادر میکند.
مشابه نحوه نظارت یک prop معمولی با watch(() => props.foo, ...)
، ما میتوانیم یک prop تجزیهشده را نیز با قرار دادن آن در یک getter نظارت کنیم:
js
watch(() => foo, /* ... */)
علاوه بر این، این روش توصیهشده زمانی است که نیاز داریم یک prop تجزیهشده را به یک تابع خارجی ارسال کنیم و واکنشپذیری آن حفظ شود:
js
useComposable(() => foo)
تابع خارجی میتواند هنگام نیاز به ردیابی تغییرات پراپ، getter را فراخوانی کند (یا آن را با toValue نرمالسازی کند)، مثلاً در یک computed یا watcher.
جزئیات پاس دادن پراپها
نگارش اسم پراپها
برای تعریف کردن اسامی پراپهای طولانی از نگارش camelCase استفاده میکنیم چرا که باعث میشود هنگام استفاده کردن از آن ها به عنوان کلید های پراپرتی نیازی به استفاده از quotes نباشد، و به ما اجازه میدهد که به صورت مستقیم در template expressions به آنها رفرنس بدهیم چرا که شناسه های معتبر جاوااسکریپت هستند.
js
defineProps({
greetingMessage: String
})
template
<span>{{ greetingMessage }}</span>
از نظر فنی، میتوانید هنگام پاس دادن پراپ به کامپوننت فرزند از نگارش camelCase استفاده کنید ( بجز in-DOM templates ). اگرچه روش معمول استفاده از نگارش kebab-case میباشد که با اتریبیوتهای HTML از یک نگارش واحد پیروی کنند.
template
<MyComponent greeting-message="hello" />
ما در صورت امکان از نگارش PascalCase برای تگهای کامپوننت استفاده میکنیم، برای اینکه شناسایی بین المنتهای بومی HTML ها و کامپوننتهای Vue راحتتر باشد. اگرچه استفاده کردن از نگارش camelCase برای پاس دادن پراپ ها خیلی سودی عَملی ندارد، به همین دلیل تصمیم گرفتیم از روش های معمول زبان ها استفاده کنیم.
پراپهای استاتیک و داینامیک
تاکنون پراپهای استاتیک را دیدهایم، به این شکل:
template
<BlogPost title="My journey with Vue" />
همچنین دیدهایم که پراپها با استفاده از v-bind
یا میانبر :
به یک مقدار داینامیک نسبت داده شدهاند، به این شکل:
template
<!-- نسبت دادن به یک متغیر بصورت داینامیک -->
<BlogPost :title="post.title" />
<!-- نسبت دادن به یک عبارت بصورت داینامیک -->
<BlogPost :title="post.title + ' by ' + post.author.name" />
انتقال مقادیر با تایپهای متفاوت
در دو مثال بالا، ما دو مقدار با تایپ رشته ( string ) پاس داده ایم، اما میتوان هر مقدار با هر تایپی به یک پراپ پاس داد.
Number
template
<!-- بگوییم Vue نیاز داریم تا به v-bind اگرچه `42` استاتیک است، به -->
<!-- این یک عبارت جاوااسکریپتی است نه یک رشته -->
<BlogPost :likes="42" />
<!-- نسبت دادن به یک متغیر بصورت داینامیک -->
<BlogPost :likes="post.likes" />
Boolean
template
<!-- است `true` نوشتن تنها نام پراپ بدون مقدار به معنی مقدار -->
<BlogPost is-published />
<!-- بگوییم Vue نیاز داریم تا به v-bind استاتیک است، به `false` اگرچه -->
<!-- یک عبارت جاوااسکریپتی است نه یک رشته -->
<BlogPost :is-published="false" />
<!-- نسبت دادن به یک متغیر بصورت داینامیک -->
<BlogPost :is-published="post.isPublished" />
Array
template
<!-- بگوییم Vue نیاز داریم تا به v-bind اگرچه یک آرایه استایتک است اما به -->
<!-- یک عبارت جاوااسکریپتی است نه یک رشته -->
<BlogPost :comment-ids="[234, 266, 273]" />
<!-- نسبت دادن به یک متغیر بصورت داینامیک -->
<BlogPost :comment-ids="post.commentIds" />
Object
template
<!-- بگوییم Vue نیاز داریم تا به v-bind اگرچه یک آبجکت استایتک است اما به -->
<!-- یک عبارت جاوااسکریپتی است نه یک رشته -->
<BlogPost
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
/>
<!-- نسبت دادن به یک متغیر بصورت داینامیک -->
<BlogPost :author="post.author" />
اتصال چندین پراپ با استفاده از آبجکت
اگر میخواهید تمام پراپرتیهای یک آبجکت را به عنوان پراپ پاس بدهید، میتوانید از v-bind
بدون آرگومان (v-bind
به جای :prop-name
). به عنوان مثال، به آبجکت post
توجه کنید:
js
const post = {
id: 1,
title: 'My Journey with Vue'
}
کد زیر:
template
<BlogPost v-bind="post" />
معادل است با:
template
<BlogPost :id="post.id" :title="post.title" />
جریان داده یک طرفه
همه پراپها دارای اتصال one-way-down بین پراپرتی کامپوننت فرزند و والد هستند: وقتی پراپرتی کامپوننت والد آپدیت میشود، باعث آپدیت شدن کامپوننت فرزند میشود، اما عکس این اتفاق نمیافتد. این از آپدیت کردن تصادفی state والد، که باعث میشود جریان انتقال داده در برنامه سختتر قابل فهم شود، در کامپوننت فرزند جلوگیری میکند.
به علاوه، هربار که کامپوننت والد آپدیت میشود، تمام پراپها در کامپوننت فرزند به آخرین و جدیدترین مقدار آپدیت میشوند. این به این معناست که نباید سعی به آپدیت کردن پراپ درون کامپوننت فرزند کرد، اگر این کار را انجام دهید، Vue در کنسول یک هشدار خواهد داد:
js
const props = defineProps(['foo'])
// ❌ warning, props are readonly!
props.foo = 'bar'
معمولا در دو حالت است که میخواهیم یک پراپ را mutate یا مقدار آن را آپدیت کنیم:
از پراپ برای مقدار دهی اولیه استفاده میشود؛ کامپوننت فرزند میخواهد از پراپ به عنوان دادهی محلی خودش استفاده کند. در این حالت بهترین راه این است که یک متغیر محلی تعریف کرده و مقدار اولیهاش را به عنوان مقدار اولیه به پراپی که میخواهیم اساین کنیم:
jsconst props = defineProps(['initialCounter']) // فقط برای مقداردهی اولیه استفاده میکند props.initialCounter از counter // و از بروزرسانیهای پراپ در آینده جدا میشود const counter = ref(props.initialCounter)
پراپی که به کامپوننت پاس داده شده است، به خودی خود قابل استفاده نیست و نیازمند اعمال تغییرات است. در این صورت، بهترین کار استفاده کردن از computed است:
jsconst props = defineProps(['size']) // بصورت خودکار آپدیت میشود props.size با هربار آپدیت شدن computed const normalizedSize = computed(() => props.size.trim().toLowerCase())
آپدیت کردن پراپهای آبجکت / آرایه
وقتی آبجکت یا آرایه به عنوان پراپ پاس داده میشوند، درحالی که کامپوننت فرزند نمیتواند پراپها را آپدیت کند، اما میتواند پراپرتیهای آبجکتها یا آرایهها را آپدیت کند. دلیل این موضوع این است که در جاوااسکریپت آبجکتها و آرایهها passed by reference هستند، و به دیلی نامعقولی هزینهی زیادی برای Vue دارد که از چنین آپدیت هایی جلوگیری کند.
اصلی ترین عیب چنین آپدیتهایی این است که اجازه میدهد دیتا و استیت کامپوننت والد را به گونهای تغییر کند که برای کامپوننت والد مشخص نیست، و به احتمال زیاد باعث سختتر شدن پیگیری و تشخیص جریان داده (data flow) در آینده میشود. بهترین راهکار این است که تا حد امکان از آپدیتهایی که به این صورت هستند دوری کرد مگر اینکه کامپوننتهای والد و فرزند نزدیکی زیادی باهم داشته باشند. در اکثر مواقع، کامپوننت فرزند باید یک event را emit کند تا به کامپوننت والد بگوید آپدیت را ممکن سازد.
اعتبارسنجی پراپها
کامپوننتها میتوانند نیازمندیهایی را برای پراپ ها مشخص کنند، به عنوان مثلا تایپ پراپ که در بالاتر به آن اشاره کردیم. اگر نیازمندی به درستی مشخص نشده باشد Vue در کنسول مرورگر هشدار خواهد داد. هنگامی که یک کامپوننت را توسعه میدهیم که تا دیگر توسعهدهندگان از آن استفاده کنند، این ویژگی به شدت مفید خواهد بود.
برای مشخص کردن نیازمندیهای یک پراپ به جای آرایهای از رشتهها، میتوان یک آبجکت با اعتبارسنجی از نیازمندی ها به ماکروی defineProps()
پاس داد.
js
defineProps({
// بررسی تایپ ساده
// `null` و `undefined` اجازه هر تایپی را می دهند
propA: Number,
// چندین تایپ ممکن
propB: [String, Number],
// تایپ رشته و الزامی
propC: {
type: String,
required: true
},
// باشد null استرینگِ الزامی ولی میتواند
propD: {
type: [String, null],
required: true
},
// تایپ عدد با مقدار پیش فرض
propE: {
type: Number,
default: 100
},
// تایپ آبجکت با مقدار پیش فرض
propF: {
type: Object,
// آبجکت یا آرایه با مقدار پیش فرض باید از طریق
// برگردانده شود factory function
// پراپ توسط کامپوننت به عنوان آرگومان دریافت میشود.
default(rawProps) {
return { message: 'hello' }
}
},
// تابع اعتبارسنجی سفارشی
// تمام پراپها به عنوان آرگومان دوم در 3.4+ پاس داده میشوند
propG: {
validator(value, props) {
// باید یکی از سه مقدار زیر باشد value
return ['success', 'warning', 'danger'].includes(value)
}
},
// تابع با مقدار پیش فرض
propH: {
type: Function,
// یک تابع سازنده نیست `default` برخلاف آبجکت یا آرایه
// این خود این تابع به عنوان یک مقدار پیش فرض به شمار میرود
default() {
return 'Default function'
}
}
})
نکته
کد داخل defineProps()
به بقیه متغیر های تعریف شده در <script setup>
دسترسی ندارد، به این دلیل که این عبارت هنگام کامپایل به یک تابع خارجی منتقل می شود.
توضیحات بیشتر:
بطور پیش فرض، دریافت همه پراپها اختیاری است، مگر اینکه
required: true
مشخص شده باشد.پراپی که تایپ آن boolean نباشد، در صورت عدم داشتن مقدار، مقدار
undefiend
خواهند داشت.عدم مقداردهی پراپ با تایپ
boolean
مقدارfalse
را خواهد داشت. میتوان این تنظیمات را با مقداردهیdefault
تغییر داد. برای مثال:default: undefined
تا رفتاری مانند تایپهای non-Boolean داشته باشد.اگر مقدار پراپ
undefiend
باشد و مقدارdefault
مشخص شده باشد، از آن استفاده خواهد شد. این مورد شامل شرایطی است که پراپ مقدار دهی نشده است یا مقدار آنundefined
پاس داده شده است.
هنگامی که اعتبار سنجی prop ناموفق باشد، Vue یک هشدار کنسول (در صورت بودن در محیط توسعه) تولید میکند.
اگر از Type-based props declarations استفاده میکنید، Vue سعی میکند تا به بهترین شکل ممکن type annotaions را با معادل runtime prop declarations :معادل سازی کند. برای مثال defineProps<{ msg: string }>
بعد از کامپایل به { msg: { type: String, required: true }}
تبدیل میشود.
Runtime Type Checks
تایپ
میتواند یکی از تایپهای زیر باشد:
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
})
Vue از instanceof Person
برای اعتبارسنجی اینکه آیا مقدار پراپ author
واقعا یک نمونه از تایپ کلاس Person
هست یا خیر استفاده میکند.
تایپ Nullable
اگر به تایپ نیاز دارید اما متغیر قابل null شدن است، میتوانید از دستور آرایه استفاده کنید که شامل null
میشود:
js
defineProps({
id: {
type: [String, null],
required: true
}
})
توجه داشته باشید که اگر type
فقط null
باشد بدون استفاده از دستور آرایه، هر تایپی را مجاز میکند.
تبدیل تایپ داده منطقی (Boolean casting)
پراپها با تایپ داده منطقی (boolean) قانون تبدیل خاصی دارند تا بتوانند مدل رفتاری اتریبیوتهای بومی مرورگر را تقلید کنند.
js
defineProps({
disabled: Boolean
})
کامپوننت به این صورت قابل استفاده است:
template
<!-- :disabled="true" معادل -->
<MyComponent disabled />
<!-- :disabled="false" معدل پاس دادن -->
<MyComponent />
هنگامی که یک برای یک پراپ چندین نوع تعریف کردهایم، قوانین تبدیل نوع داده برای Boolean
هم اعمال خواهد شد. اگرچه هنگام استفاده همزمان از دو نوع منطقی (boolean) و رشته (string) یک نکته وجود دارد، قانون تبدیل نوع داده منطقی فقط زمانی اعمال می شود که ابتدا نوع داده منطقی (boolean) قبل از رشته (string) ذکر شود.
js
// true تبدیل میشود به disabled
defineProps({
disabled: [Boolean, Number]
})
// true تبدیل میشود به disabled
defineProps({
disabled: [Boolean, String]
})
// true تبدیل میشود به disabled
defineProps({
disabled: [Number, Boolean]
})
// به یک رشته خالی تبدیل میشود disabled
defineProps({
disabled: [String, Boolean]
})