\n\n\n\n```\n\n### Зачем нужны Scoped Slots\n\n**1. Максимальная гибкость компонента**\n\nПозволяет parent контролировать представление данных:\n\n```vue\n\n\n\n\n\n\n\n```\n\n**2. Разделение логики и представления**\n\nДочерний компонент отвечает за логику, parent за представление:\n\n```vue\n\n\n\n\n\n\n\n```\n\n**3. Создание headless компонентов**\n\nКомпонент без UI - parent полностью отвечает за внешний вид:\n\n```vue\n\n\n\n\n\n\n\n```\n\n**4. Фильтры и форматирование данных**\n\nПередача фильтр-функций в slot:\n\n```vue\n\n\n\n\n\n\n\n```\n\n**5. Именованные scoped slots**\n\nМножество слотов с разными данными:\n\n```vue\n\n\n\n\n\n\n\n```\n\n### Сравнение: Scoped Slot vs Props\n\n```vue\n\n\n\n\n\n\n\n```\n\n### Вложенные scoped slots\n\n```vue\n\n\n\n\n\n```\n\n### Вывод\n\nScoped Slots нужны потому что они:\n- **Максимизируют переиспользование** - один компонент может выглядеть по-разному\n- **Разделяют логику и представление** - компонент отвечает за логику, parent за UI\n- **Создают headless компоненты** - полный контроль у parent\n- **Избегают prop drilling** - данные передаются точно туда где нужны\n- **Обеспечивают гибкость** - parent решает как отображать данные\n\nЭто один из ключевых паттернов для создания высокочеловечного и переиспользуемого кода во Vue.","dateCreated":"2026-04-02T22:12:55.306061","upvoteCount":0,"author":{"@type":"Person","name":"claude-haiku-4.5"}}}}
← Назад к вопросам

Зачем нужен Scoped Slot во Vue?

2.2 Middle🔥 231 комментариев
#Vue.js

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Scoped Slots во Vue: мощный паттерн для гибких компонентов

Scoped Slots (названные слоты с доступом к данным компонента) - это один из самых мощных паттернов во Vue для создания переиспользуемых и гибких компонентов. Они позволяют родительскому компоненту получить доступ к данным дочернего компонента и контролировать как этих данные отображаются.

Основная концепция

Обычные slots позволяют только вставить содержимое. Scoped slots позволяют передать данные из дочернего компонента в родительский:

<!-- Дочерний компонент (ChildComponent.vue) -->
<template>
  <ul>
    <!-- Передаём data в slot через v-bind -->
    <li v-for="item in items" :key="item.id">
      <slot :item="item" :index="index">
        <!-- Default содержимое если parent не определил slot -->
        {{ item.name }}
      </slot>
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' }
      ]
    };
  }
};
</script>

<!-- Родительский компонент -->
<template>
  <ChildComponent>
    <!-- Получаем доступ к item и index через v-slot -->
    <template v-slot="{ item, index }">
      <strong>{{ index + 1 }}. {{ item.name }}</strong>
    </template>
  </ChildComponent>
</template>

Зачем нужны Scoped Slots

1. Максимальная гибкость компонента

Позволяет parent контролировать представление данных:

<!-- DataTable.vue - умный список -->
<template>
  <table>
    <thead>
      <tr>
        <th v-for="column in columns" :key="column.key">
          {{ column.label }}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in data" :key="row.id">
        <td v-for="column in columns" :key="column.key">
          <!-- Передаём row и column в slot -->
          <slot :name="column.key" :row="row" :column="column">
            {{ row[column.key] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  props: ['data', 'columns']
};
</script>

<!-- Использование в родителе -->
<template>
  <DataTable :data="users" :columns="userColumns">
    <!-- Кастомное отображение email с ссылкой -->
    <template #email="{ row }">
      <a :href="`mailto:${row.email}`">{{ row.email }}</a>
    </template>
    
    <!-- Кастомное отображение статуса с цветом -->
    <template #status="{ row }">
      <span :class="{ 'text-green-600': row.status === 'active', 'text-red-600': row.status === 'inactive' }">
        {{ row.status }}
      </span>
    </template>
  </DataTable>
</template>

2. Разделение логики и представления

Дочерний компонент отвечает за логику, parent за представление:

<!-- ProductList.vue - умный список товаров -->
<template>
  <div>
    <div v-if="loading" class="spinner">Loading...</div>
    
    <div v-if="error" class="error">{{ error }}</div>
    
    <div v-if="products.length > 0">
      <!-- Передаём product с полными данными -->
      <slot 
        v-for="product in products"
        :key="product.id"
        :product="product"
        :addToCart="addToCart"
      />
    </div>
    
    <div v-else>No products</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      products: [],
      loading: true,
      error: null
    };
  },
  
  methods: {
    addToCart(product) {
      // Логика добавления в корзину
      console.log('Added to cart:', product);
    }
  },
  
  async mounted() {
    try {
      const response = await fetch('/api/products');
      this.products = await response.json();
    } catch (e) {
      this.error = e.message;
    } finally {
      this.loading = false;
    }
  }
};
</script>

<!-- Использование - parent решает как отображать -->
<template>
  <ProductList v-slot="{ product, addToCart }">
    <div class="product-card">
      <img :src="product.image" />
      <h3>{{ product.name }}</h3>
      <p>{{ product.description }}</p>
      <span class="price">${{ product.price }}</span>
      <button @click="addToCart(product)">Add to Cart</button>
    </div>
  </ProductList>
</template>

3. Создание headless компонентов

Компонент без UI - parent полностью отвечает за внешний вид:

<!-- Dropdown.vue - headless dropdown -->
<template>
  <div class="dropdown">
    <button @click="isOpen = !isOpen">
      <slot name="trigger" :isOpen="isOpen">
        Menu
      </slot>
    </button>
    
    <ul v-if="isOpen" class="dropdown-menu">
      <li v-for="item in items" :key="item.id">
        <slot :item="item" :select="selectItem">
          <button @click="selectItem(item)">
            {{ item.label }}
          </button>
        </slot>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: ['items'],
  data() {
    return { isOpen: false };
  },
  methods: {
    selectItem(item) {
      this.$emit('select', item);
      this.isOpen = false;
    }
  }
};
</script>

<!-- Использование - полный контроль над представлением -->
<template>
  <Dropdown :items="menuItems" @select="onSelect">
    <template #trigger="{ isOpen }">
      <button :class="{ 'open': isOpen }">
        More Options
      </button>
    </template>
    
    <template #default="{ item, select }">
      <button 
        @click="select(item)"
        class="custom-menu-item"
      >
        <icon :name="item.icon" /> {{ item.label }}
      </button>
    </template>
  </Dropdown>
</template>

4. Фильтры и форматирование данных

Передача фильтр-функций в slot:

<!-- UserList.vue -->
<template>
  <div>
    <div v-for="user in users" :key="user.id">
      <slot 
        :user="user"
        :formatDate="formatDate"
        :formatCurrency="formatCurrency"
      />
    </div>
  </div>
</template>

<script>
export default {
  props: ['users'],
  methods: {
    formatDate(date) {
      return new Date(date).toLocaleDateString();
    },
    formatCurrency(amount) {
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD'
      }).format(amount);
    }
  }
};
</script>

<!-- Использование -->
<template>
  <UserList :users="users" v-slot="{ user, formatDate, formatCurrency }">
    <div class="user-card">
      <h3>{{ user.name }}</h3>
      <p>Member since: {{ formatDate(user.joinDate) }}</p>
      <p>Balance: {{ formatCurrency(user.balance) }}</p>
    </div>
  </UserList>
</template>

5. Именованные scoped slots

Множество слотов с разными данными:

<!-- Form.vue -->
<template>
  <form @submit.prevent="submit">
    <slot name="header"></slot>
    
    <div class="form-fields">
      <div v-for="field in fields" :key="field.name" class="form-group">
        <!-- Каждое поле может иметь кастомное отображение -->
        <slot 
          :name="field.name"
          :field="field"
          :value="formData[field.name]"
          :error="errors[field.name]"
          :updateValue="(val) => formData[field.name] = val"
        >
          <!-- Default input -->
          <label>{{ field.label }}</label>
          <input 
            :value="formData[field.name]"
            @input="e => formData[field.name] = e.target.value"
            :type="field.type || 'text'"
          />
          <p v-if="errors[field.name]" class="error">
            {{ errors[field.name] }}
          </p>
        </slot>
      </div>
    </div>
    
    <slot name="footer" :submit="submit" :isValid="isValid">
      <button type="submit" :disabled="!isValid">Submit</button>
    </slot>
  </form>
</template>

<script>
export default {
  props: ['fields'],
  data() {
    return {
      formData: {},
      errors: {}
    };
  },
  computed: {
    isValid() {
      return Object.keys(this.errors).length === 0;
    }
  },
  methods: {
    submit() {
      this.$emit('submit', this.formData);
    }
  }
};
</script>

<!-- Использование с кастомными полями -->
<template>
  <Form :fields="fields" @submit="onSubmit">
    <template #email="{ field, value, updateValue, error }">
      <label>{{ field.label }}</label>
      <input 
        :value="value"
        @input="e => updateValue(e.target.value)"
        type="email"
        class="custom-input"
      />
      <small v-if="error" class="error">{{ error }}</small>
    </template>
    
    <template #password="{ field, value, updateValue }">
      <label>{{ field.label }}</label>
      <input 
        :value="value"
        @input="e => updateValue(e.target.value)"
        type="password"
        class="custom-input"
      />
    </template>
    
    <template #footer="{ submit, isValid }">
      <div class="button-group">
        <button @click="submit" :disabled="!isValid" class="btn-primary">
          Create Account
        </button>
        <button type="button" @click="reset" class="btn-secondary">
          Reset
        </button>
      </div>
    </template>
  </Form>
</template>

Сравнение: Scoped Slot vs Props

<!-- БЕЗ Scoped Slots - много props -->
<!-- BadList.vue -->
<template>
  <div>
    <ItemComponent
      v-for="item in items"
      :key="item.id"
      :item="item"
      :showDescription="showDescription"
      :showPrice="showPrice"
      :priceFormatter="priceFormatter"
      :descriptionTruncate="descriptionTruncate"
      @click="onItemClick"
    />
  </div>
</template>

<!-- С Scoped Slots - очень гибко -->
<!-- GoodList.vue -->
<template>
  <div>
    <div v-for="item in items" :key="item.id">
      <slot 
        :item="item"
        :formatPrice="formatPrice"
      />
    </div>
  </div>
</template>

Вложенные scoped slots

<!-- Grid.vue -->
<template>
  <div class="grid">
    <div v-for="row in rows" :key="row.id" class="grid-row">
      <div v-for="cell in row.cells" :key="cell.id" class="grid-cell">
        <!-- Вложенный slot с данными из разных уровней -->
        <slot 
          :row="row"
          :cell="cell"
          :rowIndex="rows.indexOf(row)"
          :cellIndex="row.cells.indexOf(cell)"
        />
      </div>
    </div>
  </div>
</template>

<!-- Использование -->
<template>
  <Grid :rows="gridData" v-slot="{ row, cell, rowIndex, cellIndex }">
    <div class="custom-cell">
      Cell [{{ rowIndex }},{{ cellIndex }}]: {{ cell.value }}
    </div>
  </Grid>
</template>

Вывод

Scoped Slots нужны потому что они:

  • Максимизируют переиспользование - один компонент может выглядеть по-разному
  • Разделяют логику и представление - компонент отвечает за логику, parent за UI
  • Создают headless компоненты - полный контроль у parent
  • Избегают prop drilling - данные передаются точно туда где нужны
  • Обеспечивают гибкость - parent решает как отображать данные

Это один из ключевых паттернов для создания высокочеловечного и переиспользуемого кода во Vue.

Зачем нужен Scoped Slot во Vue? | PrepBro