Introducción ¿Qué son los slots?

Los slots son un mecanismo de Vue JS que sirve para insertar contenido HTML dentro de los componentes. Es decir, con los props puedes pasar objetos y variables javascript a los componentes y con los slots puedes insertar contenido HTML dentro de otros componentes.

Imagina que quieres crear un componente que sirva para renderizar un header, y además quieres que dentro del header, a la derecha se puedan poner botones o otra información de forma que cambien dependiendo de la página. Esto se puede hacer con props, pero con los slots es mucho más sencillo y encima permites que desde fuera puedes pasar el contenido HTML que quieras.

Los slots sirven para insertar contenido en el componente hijo

Seguramente estés confuso, pero ahora con los ejemplos lo vas a ver mucho más claro.

Cómo crear slots en Vue

Sigamos con el ejemplo del botón que vimos en el artículo anterior sobre props y eventos en Vue. Imagina que ahora quieres poder añadir un icono que sea personalizable para cada botón. Una forma de abordarlo es creando un prop para pasar desde fuera el nombre del icono. Otra forma de hacerlo es con slots:

<template>
  <button @click="handleClick">
    <slot></slot>
  </button>
</template>
export default {
  methods: {
    handleClick() {
      this.$emit("click", this.example);
    }
  }
}
<script>
</script>

El slot es una etiqueta especial que tiene Vue. Cuando pones un slot lo que estás diciendo es que en ese punto vas a colocar contenido desde fuera. Fíjate que ahora no necesitamos poner un prop, porque todo el contenido que vaya dentro del botón se pasará desde fuera.

Veamos ahora cómo pasar contenido a los slots:

<template>
  <my-button @click="handleClick">
    <i class="fas fa-cat"></i>
    Botón de ejemplo
  </my-button>
</template>

import MyButton from "@/components/MyButton.vue";

export default {
  components: {
    MyButton
  },
  methods: {
    handleClick(info) {
      console.log("Click event on the button of the children with: " + info)
    }
  }
}
<script>
</script>

Todo el contenido que pongas dentro de la etiqueta HTML de un componente con slot se sustituirá dentro del componente en el lugar en el que esté colocada la etiqueta slot. Para el ejemplo anterior, el botón finalmente quedará como:

<button>
  <i class="fas fa-cat"></i>
  Botón de ejemplo
</button>

Pero no todo es bueno con los slots. La parte mala de los slots es que das demasiada libertad a la hora de usar el componente. Por ejemplo para el caso del botón, alguien podría poner dentro del slot una tabla por ejemplo haciendo que el botón se vea mal. Para este caso en concreto yo usaría un prop para el texto del botón y otro prop para el icono.

Más de un slot. Named slots

Otra cosa que se puede hacer con Vue es añadir más de un slot, para ello vas a tener que colocar un nombre a cada slot para poder indentificarlos:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

Con el atributo name le pones el nombre a cada slot. Para poder elegir qué poner en cada slot tienes que usar una etiqueta de Vue llamada template.

Los templates son unas etiquetas especiales de Vue que cuando se compila la página se eliminan dejando solo su contenido. En el ejemplo de abajo se colocará el contenido definido para cada slot en su sitio del componente eliminándose las etiquetas template y solo quedando los headings y los párrafos

<base-layout>
  <template slot="header">
    <h1>Header de la página</h1>
  </template>

  <p>Contenido de la página</p>

  <template slot="footer">
    <p>Footer de la página</p>
  </template>
</base-layout>

El ejemplo de arriba finalmente quedará renderizado como:

<div class="container">
  <header>
    <h1>Header de la página</h1>
  </header>
  <main>
    <p>Contenido de la página</p>
  </main>
  <footer>
    <p>Footer de la página</p>
  </footer>
</div>

Las etiquetas template han desaparecido. El slot del medio, el del contenido, no tiene nombre y por lo tanto será sustituido en el slot por defecto sin nombre.

Contenido por defecto del slot

También puedes poner contenido por defecto en caso de que no uses el slot:

<button type="submit">
  <slot>Submit</slot>
</button>

En caso de que al crear este componente botón no le pases contenido, Vue lo creará con el texto Sumbit.

Con esto puedes crear todo el contenido que quieras dentro de los slots por si al usar el componente no pasas nada dentro.

Scoped slots

Los scoped slots es de esas cosas que no los conoce mucha gente. Lo que permiten los scoped slots es poder pasar información desde el componente hijo al padre, es decir, desde el hijo pasas contenido al padre para que éste lo pueda pintar como necesite.

Ponte en el ejemplo de que tienes un componente que pinta una lista de usuarios. A este componente le pasas un prop con el array de usuarios a pintar. Pues bien, con los scoped slots, al hacer el v-for para pintar los usuarios, puedes pasar al componente padre, en el slot, el usuario que se está pintando en ese momento de tal forma de que en el padre decides cómo lo quieres pintar. Por ejemplo:

En el componente hijo:

<ul>
  <li v-for="(user, i) in users" :key="i">
    <slot v-bind:user="user"></slot>
  </li>
</ul>

Dentro del slot haces bind de la variable user que se está pintando para pasarla al componente padre.

En el componente padre:

<user-list>
  <template v-slot:default="slotProps">
    <span>{{ slotProps.user.firstName }}</span>
    <span>{{ slotProps.user.lastName }}</span>
  </template>
</user-list>

Recoges la variable del user en slotProps y pintas el usuario como necesites.

Por ejemplo, usando el mismo componente, también podrías tener a la vez en otro componente:

<user-list>
  <template v-slot:default="slotProps">
    <div>First name: </div>
    <div>{{ slotProps.user.firstName }}</div>
    <div>Last name: </div>
    <div>{{ slotProps.user.lastName }}</div>
  </template>
</user-list>

Esto es muy útil cuando quieres tener un componente de tabla o un componente que pinte una lista o colección de elementos y necesitas dos formas diferentes de pintarlos. Recogiendo el valor en los componentes padres puedes decidir cómo vas a querer renderizarlo usando un solo componente hijo.

Conslusiones

Como he dicho, yo personalmente recomiendo crear los componentes usando props y no slots porque así limitas más el contenido de los componentes para que no queden cosas extrañas. De todas formas para ciertos componentes sobre todo estructurales en los que necesitas pasar mucho contenido HTML su uso si que está más justificado.

En próximos capítulos veremos como estructurar los proyectos de Vue para poder mandar peticiones HTTP a un servidor o API y así poder conectar a las aplicaciones creadas en Vue con un servidor con bases de datos.