En Vue.js 3, la comunicación entre componentes es esencial para construir aplicaciones bien organizadas. En este artículo veremos varías formas como son los props, events, provide/inject, el uso de un estado global como Pinia y el uso del sistema de slots. Veremos también cuando utilizar una u otra.

1. Props

Los Props (propiedades) son la forma más común de pasar datos de un componente padre a un componente hijo.

Ejemplo: Contamos dos componentes, el padre y el hijo, en el padre creamos una variable string llamada "parentMessage" a la que le daremos un valor, y la pasaremos por prop mediante el prop "message" al componente hijo.

Componente Padre (ParentComponent.vue):

<template>
  <ChildComponent :message="parentMessage" />
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const parentMessage = ref('Hello from Parent!');
</script>

Componente hijo (ChildComponent.vue):

<template>
  <div>{{ message }}</div>
</template>

<script setup>
import { defineProps } from 'vue';

//Definición del prop
const props = defineProps({
  message: {
    type: String,
    required: true
  }
});
</script>

2. Eventos

Los eventos son la forma principal de comunicar datos desde un componente hijo hacia un componente padre utilizando el sistema de eventos de Vue.

Ejemplo de Uso de los eventos: en el componente hijo definimos el evento "child-event" y lo lanzaremos mediante la función sendEvent. El padre, a la hora de añadir el hijo en su template, definirá que función debe ejecutarse cuando el hijo lance el evento.

Componente Padre (ParentComponent.vue):

<template>
  <ChildComponent @child-event="handleEvent" />
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';

const handleEvent = (payload) => {
  console.log('Event received:', payload);
}
</script>

Componente Hijo (ChildComponent.vue):

<template>
  <button @click="sendEvent">Click me</button>
</template>

<script setup>
import { defineEmits } from 'vue';

const emit = defineEmits(['child-event']);

const sendEvent = ()  => {
  emit('child-event', 'Hello from Child!');
}
</script>

3. Provide/Inject

Provide/Inject permite que un componente "provea" datos o métodos a cualquier componente descendiente sin tener que pasar datos a través de props en cada nivel intermedio.

Ejemplo de Uso de Provide/Inject: Imaginemos que queremos pasar información desde el componente App.vue a un componente que se encuentra 4 veces por debajo de este. Haremos uso de provide para definir que datos queremos que estén disponibles para los componentes descendientes y mediante inject accederemos a estos datos.

Componente Ancestro (App.vue):

<template>
  <DescendantComponent />
</template>

<script setup>
import { provide } from 'vue';
import DescendantComponent from './DescendantComponent.vue';

provide('sharedData', 'Data from App.vue');
</script>

Componente Descendiente (DescendantComponent.vue):

<template>
  <div>{{ sharedData }}</div>
</template>

<script setup>
import { inject } from 'vue';

const sharedData = inject('sharedData');
</script>

4. Estado Global (Pinia)

Para aplicaciones más grandes, gestionar el estado global es crucial. Pinia es una biblioteca moderna para manejar el estado global de la aplicación.

Ejemplo de Uso de Pinia: El ejemplo más típico es una tienda online, donde accedes al estado global para pintar el número de artículos que hay en el carrito.

Configuración de Pinia (store.js):

import { defineStore } from 'pinia';

export const useMainStore = defineStore('main', {
  state: () => ({
    message: 'Hello from Pinia!'
  }),
  actions: {
    updateMessage(newMessage) {
      this.message = newMessage;
    }
  }
});

Componente que Usa Pinia (Component.vue):

<template>
  <div>{{ message }}</div>
  <button @click="store.updateMessage('Updated Message!')">Update Message</button>
</template>

<script setup>
import { useMainStore } from './store';
import { storeToRefs } from 'pinia';

const store = useMainStore();
const { message } = storeToRefs(store);
</script>

5. Slots

Los slots permiten que un componente padre inserte contenido dentro de un componente hijo. Son muy útiles para crear componentes flexibles y reutilizables.

Ejemplo de Uso de Slots: Un ejemplo podría ser una card, dónde quieres añadir diferente contenido, en ocasiones puedes añadir texto y un entras imagen, o ambas

Componente Padre (ParentComponent.vue):

<template>
  <ChildComponent>
    <template #default>
      <p>This is a slot content from Parent.</p>
    </template>
  </ChildComponent>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
</script>

Componente Hijo (ChildComponent.vue):

<template>
  <div>
    <slot></slot>
  </div>
</template>

<script setup>
</script>