Back to Airi

Avoid Excessive Component Abstraction in Large Lists

.agents/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md

0.10.14.3 KB
Original Source

Avoid Excessive Component Abstraction in Large Lists

Impact: MEDIUM - Component instances are more expensive than plain DOM nodes. While abstractions improve code organization, unnecessary nesting creates overhead. In large lists, this overhead multiplies - 100 items with 3 levels of abstraction means 300+ component instances instead of 100.

Don't avoid abstraction entirely, but be mindful of component depth in frequently-rendered elements like list items.

Task List

  • Review list item components for unnecessary wrapper components
  • Consider flattening component hierarchies in hot paths
  • Use native elements when a component adds no value
  • Profile component counts using Vue DevTools
  • Focus optimization efforts on the most-rendered components

BAD:

vue
<!-- BAD: Deep abstraction in list items -->
<template>
  <div class="user-list">
    <!-- For 100 users: Creates 400 component instances -->
    <UserCard v-for="user in users" :key="user.id" :user="user" />
  </div>
</template>

<!-- UserCard.vue -->
<template>
  <Card>  <!-- Wrapper component #1 -->
    <CardHeader>  <!-- Wrapper component #2 -->
      <UserAvatar :src="user.avatar" />  <!-- Wrapper component #3 -->
    </CardHeader>
    <CardBody>  <!-- Wrapper component #4 -->
      <Text>{{ user.name }}</Text>
    </CardBody>
  </Card>
</template>

<!-- Each UserCard creates: Card + CardHeader + CardBody + UserAvatar + Text
     100 users = 500+ component instances -->

GOOD:

vue
<!-- GOOD: Flattened structure in list items -->
<script setup>
defineProps({
  user: Object
})
</script>

<!-- UserCard.vue - Flattened, uses native elements -->
<template>
  <div class="user-list">
    <!-- For 100 users: Creates 100 component instances -->
    <UserCard v-for="user in users" :key="user.id" :user="user" />
  </div>
</template>

<template>
  <div class="card">
    <div class="card-header">
      
    </div>
    <div class="card-body">
      <span class="user-name">{{ user.name }}</span>
    </div>
  </div>
</template>

<style scoped>
/* Styles that would have been in Card, CardHeader, etc. */
.card { /* ... */ }
.card-header { /* ... */ }
.card-body { /* ... */ }
.avatar { /* ... */ }
</style>

When Abstraction Is Still Worth It

vue
<!-- Component abstraction is valuable when: -->

<!-- 1. Complex behavior is encapsulated -->
<UserStatusIndicator :user="user" />  <!-- Has logic, tooltips, etc. -->

<!-- 2. Reused outside of the hot path -->
<Card>  <!-- OK to use in one-off places, not in 100-item lists -->

<!-- 3. The list itself is small -->
<template v-if="items.length < 20">
  <FancyItem v-for="item in items" :key="item.id" />
</template>

<!-- 4. Virtualization is used (only ~20 items rendered at once) -->
<RecycleScroller :items="items">
  <template #default="{ item }">
    <ComplexItem :item="item" />  <!-- OK - only 20 instances exist -->
  </template>
</RecycleScroller>

Measuring Component Overhead

javascript
// In development, profile component counts
import { getCurrentInstance, onMounted } from 'vue'

onMounted(() => {
  const instance = getCurrentInstance()
  let count = 0

  function countComponents(vnode) {
    if (vnode.component)
      count++
    if (vnode.children) {
      vnode.children.forEach((child) => {
        if (child.component || child.children)
          countComponents(child)
      })
    }
  }

  // Use Vue DevTools instead for accurate counts
  console.log('Check Vue DevTools Components tab for instance counts')
})

Alternatives to Wrapper Components

vue
<!-- Instead of a <Button> component for styling: -->
<button class="btn btn-primary">
Click
</button>

<!-- Instead of a <Text> component: -->
<span class="text-body">
{{ content }}
</span>

<!-- Instead of layout wrapper components in lists: -->
<div class="flex items-center gap-2">
  <!-- content -->
</div>

<!-- Use CSS classes or Tailwind instead of component abstractions for styling -->

Impact Calculation

List SizeComponents per ItemTotal InstancesMemory Impact
100 items1 (flat)100Baseline
100 items3 (nested)300~3x memory
100 items5 (deeply nested)500~5x memory
1000 items1 (flat)1000High
1000 items5 (deeply nested)5000Very High