image
https://unsplash.com/photos/WJyCRU1m7h8

Mechanizm slotów, omawiany w tym artykule jest bardzo przydatny podczas budowania elastycznych widoków. W tym wpisie pokazuję podstawowe przypadki użycia oraz główne funkcje.

Na wstępie chciałbym zwrócić uwagę na jedną istotną rzecz. W Vue 2.6 został opublikowany nowy, ujednolicony sposób definiowania i używania slotów. Jeśli w innych źródłach spotkasz się z odmienną składnią, zwróć uwagę o której wersji Vue jest mowa, gdyż o ile teraz kompatybilność wsteczna jest zachowana, twórcy zastrzegają, że prawdopodobnie od wersji 3.0 zostanie porzucona.

Podstawy

Na początek zdefiniujmy założenia, a następnie stwórzmy nowy komponent, który rozbudujemy w kolejnych krokach.

Przyjmijmy, że chcemy mieć uniwersalny wrapper, który będzie trzymał resztę elementów UI wraz z opisem oraz innym układem w zależności od parametrów (podziałem na sekcje) itd.

Nowy komponent PageBlock na początek będzie wyglądał następująco (najważniejszy jest element <slot>):

// PageBlock.vue
<template>
  <div class="page-block">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "page-block"
};
</script>

<style lang="scss">
.page-block {
  padding: 1rem;
}
</style>

Zmieńmy zawartość komponentu HelloWorld na poniższą, a w części <script> zaimportujmy nowy komponent.

// HelloWorld.vue
<template>
  <div class="products">
+    <page-block>
      <product 
        v-for="product in products" 
        :product="product" 
        :key="product.id" 
        @add="onAddToCart"
      />
+    </page-block>
  </div>
</template>

W efekcie nasza lista produktów została umieszczona w elemencie <div class="page-block">.

Sloty nazwane

Rozbudujmy teraz PageBlock.vue o opcjonalny nagłówek z tytułem sekcji oraz footerem.

// PageBlock.vue
<template>
  <div class="page-block">
    <div class="title">
      <slot name="title"></slot>
    </div>

    <slot>Empty content</slot>

    <div class="footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

Zostały dodane kolejne sloty z atrybutem name, przez który możemy wskazać do którego slotu wstawić dane. Slot bez nazwy jest traktowany jako domyślny i zostanie użyty, jeśli nie zostanie podana żadna nazwa. Dodatkowo, zawartość wstawiona pomiędzy znacznik <slot></slot> jest traktowana jako "fallback", czyli treść alternatywna, która zostanie użyta jeśli nie zostanie podane nic innego.

Zobaczmy jak użyć zmodyfikowany komponent.

<template>
  <div class="products">
    <page-block>
      <template v-slot:title>
        <h1>Products list</h1>
      </template>

      <product 
        v-for="product in products" 
        :product="product" 
        :key="product.id" 
        @add="onAddToCart"
      />

      <template v-slot:footer>
        My footer
      </template>
    </page-block>
  </div>
</template>

Kilka uwag:

  • w nowej notacji v-slot możemy użyć tylko na elemencie <template>,
  • treść wstawiona poza elementy <template> traktowana jest jako domyślna i dla lepszej czytelności może zostać zawarta w <template v-slot:default>,
  • niezawarcie bloku np. z footerem spowoduje, że ten slot zostanie pominięty, lecz w przypadku slotu domyślnego zostanie wstawiony tekst Empty content.

Zasięg zmiennych

Należy pamietać o rozróżnieniu zasięgów (scope'ów) pomiędzy rodzicem, a dzieckiem. Komponenty, nie współdzielą zmiennych, zapewniając w ten sposób hermetyzację danych. W związku z tym, w poniższym przykładzie, dane przekazane przez props do komponentu PageBlock będą dostępne tylko w tym komponencie. Zobaczmy to na przykładzie:

// PageBlock.vue
<template>
  <div class="page-block" :class="isWide && 'page-block-wide'">
    <div class="title">
      <slot name="title"></slot>
    </div>
    <slot>Empty content</slot>
    <div class="footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "page-block",
  props: {
    isWide: {
      type: Boolean,
      required: false,
      default: false,
    }
  }
};
</script>

W PageBlock.vue definiuję obiekt props ze zmienną isWide, która jest dostępna w tym komponencie. Natomiast poniższe użycie zakończy komunikatem błędu, ponieważ zmienna isWide jest undefined w komponencie HelloWorld .

<template>
  <div class="products">
    <page-block :isWide="true">
      {{ isWide }} <!-- undefined  -->
      <template v-slot:title>
        <h1>Products list</h1>
      </template>
      <product 
        v-for="product in products" 
        :product="product" 
        :key="product.id" 
        @add="onAddToCart"
      />
      <template v-slot:footer>
        My footer
      </template>
    </page-block>
  </div>
</template>
[Vue warn]: Property or method "isWide" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.


found in


---> <HelloWorld>
       <App> at src/App.vue
         <Root>

Skrócone nazewnictwo

W celu skrócenia i uproszczenia można zastąpić v-slot: znakiem #. Wtedy slot o nazwie title będzie wyglądał następująco:

<template #title>
    ...
</template>

Podsumowanie

Sloty są powszechnie używane w wielu bibliotekach do UI np. Bootstrap, dlatego warto znać ich możliwości. Ponadto, dobrze użyte pozwalają poprawić jakość kompozycji naszych widoków.

Ten artykuł, jest na dobrą sprawę jedynie wprowadzeniem do mechanizmu slotów, dlatego zachęcam Cię do dalszego zgłębienia tematu.