Provide / Inject en VueJS

Una gran herramienta que nos provee VueJS para hacer que nuestro código sea más mantenible en ciertas situaciones.


Introducción

En esta ocasión explicaremos la herramienta provide/inject que nos brinda Vue. Nos será de utilidad en ciertas situaciones. Usualmente se utiliza cuando existe una cantidad elevada de niveles en la jerarquía de componentes con Vue.

Prop Drilling

Generalmente, cuando tenes que pasar datos desde un componente padre a uno hijo, utilizamos props. Sin embargo, imagina la situación que tienes un árbol de componentes bastante extenso, y un componente que se encuentra en un nivel muy profundo de este árbol, necesita algo (usualmente datos) de un componente superior o anscestro. Si solo utilizaramos props, tendríamos que pasar el mismo prop por cada componente hijo hasta llegar al deseado.

Prop Drilling VueJS español
Tomado de VueJS Guide

Observa como el componente <Footer> no hace uso de la prop, sino que funciona como un pasa manos para el componente <DeepChild>. Básicamente, en el componente <Footer> se define una prop y ésta es pasada a <DeepChild>, que a su vez esta última define una prop con las mismas características que la de <Footer>. Ahora, ¿Te imáginas que podría suceder si esta cadena de componentes sería mayor?

Lo descrito anteriormente (el pasamanos de props en una cadena de componentes) es denominado Prop Drilling. Podemos resolver el prop drilling con provide e inject.

Con Prop Drilling estamos repitiendo la misma definición de una prop en varios componentes, esto va encontra del DRY (Don’t Repeat Yourself). Esto trae como consecuencia que si en algún momento en el futuro deseasemos cambiar la prop (su type por ejemplo), tendríamos que cambiar cada definición de la prop en cada componente involucrado. Que divertido… 😅

Provide / Inject

Esta técnica funciona de la siguiente manera: un componente padre dispone (o provee) dependencias para todos sus componentes descendientes. Cualquier componente descendiente en el árbol, independientemente de su profundidad (o nivel), puede inyectar dependencias que fueron provistas (o dispuestas) por componentes superiores.

Provide Inject VueJS español
Tomado de VueJS Guide

<Root> provee dependencias a sus componentes descendientes. El componente <DeepChild> inyecta (o consume) dependencias del componente ansestro <Root>.


Provide

Para proporcionar algún dato a componentes descendientes se utiliza la función provide():

<script setup>
    import { provide } from 'vue';
    provide('mensaje', 'Hola mundo'); // provide(<key>, <value>)
</script>

La función provide() toma dos argumentos. El primero es la key de la inyección (injection key). Esta clave es utilizada por componentes descendientes para obtener el valor correspondiente de la inyección. Un componente puede invocar la función provide() las veces que quiera con diferentes keys para proveer distintos valores.

El segundo argumento es el valor que se provee. El valor puede ser de cualquier tipo, incluyendo estado reactivo como los refs:

<script setup>
    import { provide, ref } from 'vue';
    const contadorClicks = ref(0);
    provide('mensaje', 'Hola universo');
    provide('contador', contadorClicks);
</script>

Inject

Para consumir datos que fueron proporcionados por un componente anscestro, se utiliza la función inject() en uno o varios componentes descendientes:

<script setup>
    import { inject } from 'vue';
    const contador = inject('contador')
</script>

Valores por defecto

En caso de que no estemos seguros de que exista un componente anscestro que proporcione datos con provide(), tenemos la posibilidad de definir valores por defecto en la función inject(<key>, <default_value>):

<script setup>
    import { inject } from 'vue';
    const nombreCliente = inject('nombreCliente', 'Sin nombre')
</script>

Si no se proporciona nombreCliente, el valor de la variable será el string 'Sin Nombre'.

Utilizando la Reactividad de Vue

Cuando utilizamos provide / inject, se recomienda mantener las mutaciones de estado reactivo dentro del componente que invoca provide(). Esto nos asegura de que los datos proporcionados y sus posibles mutaciones están localizadas dentro del mismo componente, haciendo que sea mucho más fácil el mantenimiento de nuestro código.

Sin embargo, puede suceder que necesitemos actualizar datos desde un componente consumidor (el que ejecuta inject). En estos casos, se sugiere proporcionar una función que sea responsable de mutar el estado desde el componente anscestro:

<!-- Dentro del componente que provee (anscestro) -->
<script setup>
    import { provide, ref } from 'vue';
    
    const ubicacion = ref('Argentina')

    function actualizarUbicacion (nuevaUbicacion) {
        ubicacion.value = nuevaUbicacion
    }

    provide('ubicacion', {
        ubicacion,
        actualizarUbicacion
    })
</script>
<!-- Dentro del componente que inyecta (descendiente) -->
<script setup>
    import { inject } from 'vue';
    
    const { ubicacion, actualizarUbicacion } = inject('ubicacion')

    console.log(ubicacion)
    actualizarUbicacion('Uruguay')
    console.log(ubicacion)
</script>

Además, si deseamos asegurarnos de que los datos pasados a través de provide() no puedan ser mutados por componentes descendientes, podemos utilizar readonly():

<script setup>
    import { provide, ref, readonly } from 'vue';

    const contador = ref(0)
    provide('contador-solo-lectura', readonly(contador))
</script>

Conclusión

Provide / Inject es una herramienta versatil que facilita la comunicación entre componentes en aplicaciones de Vue.js. Ofrece una solución elegante para mitigar el Prop Drilling y simplifica el trabajo a la hora de compartir datos entre componentes que se encuentran en un nivel profundo en el árbol de componentes.

Referencias