Skip to content

رِندر لیست - List Rendering

v-for

ما می‌توانیم از دستور v-for برای نمایش یک لیست از آیتم‌ها، بر اساس یک آرایه استفاده کنیم. دستور v-for نیاز به یک سینتکس ویژه به شکل item in items دارد، جایی که items آرایه منبع و item نام مستعار برای عنصر آرایه‌ای است که در حال حلقه زدن بر آن هستیم:

js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
js
data() {
  return {
    items: [{ 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' }])
js
data() {
  return {
    parentMessage: 'Parent',
    items: [{ message: 'Foo' }, { message: 'Bar' }]
  }
}
template
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
  • Parent - 0 - Foo
  • Parent - 1 - Bar
  • دقت کنید در مثال بالا دایره برای المنت <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.keys()‎ روی آبجکت خواهد بود:

    js
    const myObject = reactive({
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    })
    js
    data() {
      return {
        myObject: {
          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 لیستی از کامپوننت‌ها را رندر کرده و داده‌های متفاوتی به هر نمونه ارسال کنید.

    این مثال لیست کارها را ببینید تا بیاموزید چگونه با استفاده از 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/))
    js
    this.items = this.items.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)
    })
    js
    data() {
      return {
        numbers: [1, 2, 3, 4, 5]
      }
    },
    computed: {
      evenNumbers() {
        return this.numbers.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)
    }
    js
    data() {
      return {
        sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
      }
    },
    methods: {
      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()
    رِندر لیست - List Rendering has loaded