رِندر لیست - List Rendering
v-for
ما میتوانیم از دستور v-for
برای نمایش یک لیست از آیتمها، بر اساس یک آرایه استفاده کنیم. دستور v-for
نیاز به یک سینتکس ویژه به شکل item in items
دارد، جایی که items
آرایه منبع و item
نام مستعار برای عنصر آرایهای است که در حال حلقه زدن بر آن هستیم:
js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="item in items">
{{ item.message }}
</li>
درون اِسکوپ v-for
، عبارات تمپلیت (آنچه داخل v-for
مینویسیم) دسترسی به همه مشخصههای اِسکوپ والد را دارند. علاوه بر این، v-for
همچنین از یک پارامتر دوم اختیاری برای اَندیس آیتم فعلی پشتیبانی میکند:
js
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
دقت کنید در مثال بالا دایره برای المنت <li> </li>
میباشد امتحان این مورد در Playground
اِسکوپ متغیر در v-for
مشابه کد جاوااسکریپت زیر است:
js
const parentMessage = 'Parent'
const items = [
/* ... */
]
items.forEach((item, index) => {
// دارد `parentMessage` دسترسی به متغیر بیرونی
// فقط در اینجا در دسترس هستند `index` و `item` اما
console.log(parentMessage, item.message, index)
})
توجه کنید که مقدار v-for
با امضای تابع forEach
همخوانی دارد. در واقع، میتوانید برای نماینده مورد استفاده در v-for
از تخریب (اشاره به destructuring) متشابه با تخریب آرگومانهای تابع استفاده کنید.
template
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- with index alias -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
برای v-for
تو در تو، اِسکوپ متغیر هم مشابه توابع تو در تو عمل میکند. هر اِسکوپ v-for
دسترسی به اِسکوپ والد و بالاتر دارد:
template
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
همچنین میتوانید به جای in
از of
به عنوان جداکننده استفاده کنید تا به سینتکس جاوااسکریپت برای iterator نزدیکتر باشد:
template
<div v-for="item of items"></div>
v-for
با یک آبجکت
همچنین میتوانید از v-for
برای مرور کلیدهای یک آبجکت استفاده کنید. ترتیب تکرار بر اساس نتیجه تابع Object.values()
روی آبجکت خواهد بود:
js
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
template
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
همچنین میتوانید یک نام مستعار دیگر برای نام کلید ارائه دهید:
template
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
و یکی دیگر برای اَندیس:
template
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
v-for
با یک محدوده مشخص
v-for
همچنین میتواند یک عدد صحیح بگیرد. در این حالت، الگو مورد نظر به تعداد آن تکرار میشود، بر اساس یک محدوده از 1...n
.
template
<span v-for="n in 10">{{ n }}</span>
توجه کنید که در اینجا n
با مقدار اولیه 1
به جای 0
شروع میشود.
v-for
روی <template>
مشابه v-if
، میتوانید از تگ <template>
با v-for
برای رندر کردن یک بلوک از چندین المان استفاده کنید. برای مثال:
template
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
v-if
با v-for
توجه داشته باشید
استفاده از v-if
و v-for
روی یک عنصر به دلیل اولویت ضمنی، توصیه نمیشود. برای جزئیات به style guide مراجعه کنید.
وقتی هر دو روی یک نود وجود داشته باشند، v-if
اولویت بالاتری نسبت به v-for
دارد. این بدان معناست که شرط v-if
دسترسی به متغیرهای درون اِسکوپ v-for
نخواهد داشت:
template
<!--
"todo" خطا میدهد چون خاصیت
بر روی نمونه تعریف نشده است
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
این مشکل با انتقال v-for
به یک تگ <template>
(که واضحتر هم هست) حل میشود:
template
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
حفظ وضعیت با key
وقتی Vue لیستی از المانهای رندرشده با v-for
را بهروزرسانی میکند، به طور پیشفرض از استراتژی پچ در محل (in-place patch) استفاده میکند. اگر ترتیب آیتمهای داده تغییر کرده باشد، به جای جابجایی المانهای DOM برای مطابقت با ترتیب آیتمها، Vue هر المان را د رجای قبلی خود اصلاح میکند و مطمئن میشود که محتوای رندرشده در آن اندیس خاص درست است.
این حالت پیشفرض بهینه است، اما فقط وقتی مناسب است که خروجی رندر لیست شما به state کامپوننت فرزند یا state موقتی DOM (مثل مقادیر فرم) وابسته نباشد.
برای اینکه به Vue کمک کنید هویت هر نود را دنبال کند و در نتیجه المانهای موجود را در استفاده مجدد مرتب کند، نیاز است برای هر آیتم یک خاصیت key
منحصربفرد ارائه دهید:
template
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
وقتی از <template v-for>
استفاده میکنید، key
باید روی خود تگ <template>
قرار بگیرد:
template
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
توجه
key
اینجا یک خاصیت ویژهای است که با v-bind
پیاده سازی شده است. نباید آن را با متغیر کلیدی key هنگام استفاده از v-for با یک آبجکت اشتباه بگیرید.
توصیه میشود هر جا امکان دارد یک خاصیت key
با v-for
ارائه دهید، مگر اینکه محتوای DOM تکرارشونده ساده باشد (هیچ اجزاء یا المانهای DOM حاوی state نباشد) یا عمداً برای افزایش عملکرد به رفتار پیش فرض تکیه می کنید. (مترجم: همه جا از key
استفاده کنید چون از آینده خبر ندارید و نمیدونید چه بلایی قراره سر کدی که شما نوشتید بیاد 😃)
خاصیت key
مقادیر اولیه - یعنی رشتهها و اعداد - را انتظار دارد. از آبجکتها به عنوان کلید v-for
استفاده نکنید. برای استفاده جزئی از خاصیت key
لطفا به مستندات API key مراجعه کنید. (مترجم: از index
درون v-for
به عنوان key
استفاده نکنید)
v-for
بههمراه کامپوننت
این بخش به پیشنیاز کامپوننتها نیاز دارد. اگر میخواهید میتوانید آن را رد کنید و بعدا برگردید.
میتوانید مستقیما v-for
را روی یک کامپوننت، مانند هر المان عادی، استفاده کنید (فراموش نکنید key
ارائه دهید):
template
<MyComponent v-for="item in items" :key="item.id" />
اما این به طور خودکار دادهای را به کامپوننت منتقل نمیکند، چرا که کامپوننتها اِسکوپ مستقل خودشان را دارند. برای منتقل کردن داده به کامپوننت، باید از props هم استفاده کنیم:
template
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
دلیل اینکه item
به طور خودکار به کامپوننت تزریق نمیشود این است که این کار باعث وابستگی شدید کامپوننت به نحوه کارکرد v-for
میشود. مشخص کردن صریح منبع داده باعث میشود کامپوننت در سایر موقعیتها نیز قابل استفاده مجدد باشد.
این مثال لیست کارها را ببینید تا بیاموزید چگونه با استفاده از v-for
لیستی از کامپوننتها را رندر کرده و دادههای متفاوتی به هر نمونه ارسال کنید.
تشخیص تغییرات آرایه
متدهای ایجاد تغییر
Vue میتواند تشخیص دهد که کدام متد برای تغییر محتوا یک آرایه واکنشگرا (reactive) صدا زده شده و بهروزرسانیهای لازم را اعمال کند. این متدها عبارتند از:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
جایگزینی یک آرایه
متدهای تغییر محتوا روی آرایهای که روی آن صدا زده میشوند تغییر ایجاد میکنند، همانطور که از نامشان پیداست. در مقابل، روشهای غیرجهشی مانند filter()
، concat()
و slice()
روی آرایه اصلی تغییر ایجاد نمیکنند بلکه همیشه آرایهای جدید برمیگردانند. هنگام کار با این روشها باید آرایه قدیمی را با آرایه جدید جایگزین کنیم:
js
// با مقدار آرایه است ref یک `items`
items.value = items.value.filter((item) => item.message.match(/Foo/))
شاید فکر کنید این کار باعث میشود Vue تمام DOM موجود را دور بریزد و کل لیست را دوباره رندر کند - خوشبختانه اینطور نیست. Vue الگوریتمهای هوشمندی را برای حداکثر استفاده مجدد از المانهای DOM پیادهسازی کرده است، بنابراین جایگزین کردن یک آرایه با آرایهای دیگر که حاوی آبجکتهای تکراری است یک عملیات سبک است.
نمایش نتایج فیلتر شده / مرتب شده
گاهی اوقات میخواهیم نسخهای فیلتر یا مرتبشده از یک آرایه را نمایش دهیم بدون اینکه واقعا روی دادههای اصلی تغییر ایجاد کنیم یا آنها را ریست کنیم. در این موارد میتوانیم یک computed بسازیم که آرایه فیلتر یا مرتبشده را برمیگرداند.
برای مثال:
js
const numbers = ref([1, 2, 3, 4, 5])
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
template
<li v-for="n in evenNumbers">{{ n }}</li>
در موقعیتهایی که از پراپرتیهای computed امکانپذیر نیست (مثلا درون حلقههای تودرتوی v-for
)، میتوانید از یک متد استفاده کنید:
js
const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
function even(numbers) {
return numbers.filter((number) => number % 2 === 0)
}
template
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}</li>
</ul>
هنگام استفاده از reverse()
و sort()
در یک پراپرتی computed مراقب باشید! این دو متد روی آرایه اصلی تغییر ایجاد میکنند که در getterهای computed باید از آن اجتناب کرد. قبل از صدا زدن این متدها، یک کپی از آرایه اصلی را بسازید. (مانند کد زیر)
diff
- return numbers.reverse()
+ return [...numbers].reverse()