Uso de tragamonedas en Vue.js

Publicado: 2022-03-10
Resumen rápido ↬ Las ranuras son una herramienta poderosa para crear componentes reutilizables en Vue.js, aunque no son la característica más simple de entender. Echemos un vistazo a cómo usar las ranuras y algunos ejemplos de cómo se pueden usar en sus aplicaciones Vue.

Con el reciente lanzamiento de Vue 2.6, la sintaxis para usar tragamonedas se ha simplificado más. Este cambio en las tragamonedas me ha vuelto a interesar en descubrir el poder potencial de las tragamonedas para brindar reutilización, nuevas funciones y una legibilidad más clara para nuestros proyectos basados ​​en Vue. ¿De qué son realmente capaces las tragamonedas?

Si es nuevo en Vue o no ha visto los cambios de la versión 2.6, siga leyendo. Probablemente el mejor recurso para aprender sobre tragamonedas es la propia documentación de Vue, pero intentaré dar un resumen aquí.

¿Qué son las tragamonedas?

Las ranuras son un mecanismo para los componentes de Vue que le permite componer sus componentes de una manera diferente a la estricta relación padre-hijo. Las tragamonedas le brindan una salida para colocar contenido en lugares nuevos o hacer que los componentes sean más genéricos. La mejor manera de entenderlos es verlos en acción. Comencemos con un ejemplo simple:

 // frame.vue <template> <div class="frame"> <slot></slot> </div> </template>

Este componente tiene un contenedor div . Supongamos que div está ahí para crear un marco estilístico alrededor de su contenido. Este componente se puede usar de forma genérica para envolver un marco alrededor de cualquier contenido que desee. Veamos cómo se ve usarlo. El componente del frame aquí se refiere al componente que acabamos de hacer arriba.

 // app.vue <template> <frame><img src="an-image.jpg"></frame> </template>

El contenido que se encuentra entre las etiquetas frame de apertura y cierre se insertará en el componente frame donde está la slot , reemplazando las etiquetas slot . Esta es la forma más básica de hacerlo. También puede especificar el contenido predeterminado para que entre en un espacio simplemente completándolo:

 // frame.vue <template> <div class="frame"> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>

Así que ahora si lo usamos así en su lugar:

 // app.vue <template> <frame /> </template>

Aparecerá el texto predeterminado "Este es el contenido predeterminado si no se especifica nada para ir aquí", pero si lo usamos como lo hicimos antes, la etiqueta img anulará el texto predeterminado.

¡Más después del salto! Continúe leyendo a continuación ↓

Ranuras Múltiples/Con Nombre

Puede agregar varias ranuras a un componente, pero si lo hace, todas menos una deben tener un nombre. Si hay uno sin nombre, es la ranura predeterminada. Así es como se crean varias ranuras:

 // titled-frame.vue <template> <div class="frame"> <header><h2><slot name="header">Title</slot></h2></header> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>

Mantuvimos el mismo espacio predeterminado, pero esta vez agregamos un espacio llamado header donde puede ingresar un título. Lo usas así:

 // app.vue <template> <titled-frame> <template v-slot:header> <!-- The code below goes into the header slot --> My Image's Title </template> <!-- The code below goes into the default slot --> <img src="an-image.jpg"> </titled-frame> </template>

Al igual que antes, si queremos agregar contenido a la ranura predeterminada, simplemente colóquelo directamente dentro del componente titled-frame . Sin embargo, para agregar contenido a una ranura con nombre, necesitábamos envolver el código en una etiqueta de template con una directiva v-slot Agrega dos puntos ( : después v-slot y luego escribe el nombre de la ranura a la que desea pasar el contenido. Tenga en cuenta que v-slot es nuevo en Vue 2.6, por lo que si está utilizando una versión anterior, deberá leer los documentos sobre la sintaxis de ranura en desuso.

Tragamonedas con alcance

Una cosa más que deberá saber es que las máquinas tragamonedas pueden pasar datos/funciones a sus hijos. Para demostrar esto, necesitaremos un componente de ejemplo completamente diferente con ranuras, uno que sea incluso más artificial que el anterior: copiemos el ejemplo de los documentos creando un componente que suministre los datos sobre el usuario actual a sus ranuras:

 // current-user.vue <template> <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> </template> <script> export default { data () { return { user: ... } } } </script>

Este componente tiene una propiedad llamada user con detalles sobre el usuario. De forma predeterminada, el componente muestra el apellido del usuario, pero tenga en cuenta que utiliza v-bind para vincular los datos del usuario a la ranura. Con eso, podemos usar este componente para proporcionar los datos del usuario a su descendiente:

 // app.vue <template> <current-user> <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template> </current-user> </template>

Para obtener acceso a los datos pasados ​​a la ranura, especificamos el nombre de la variable de alcance con el valor de la directiva v-slot .

Hay algunas notas para tomar aquí:

  • Especificamos el nombre de default , aunque no es necesario para la ranura predeterminada. En su lugar, podríamos usar v-slot="slotProps" .
  • No necesita usar slotProps como nombre. Puedes llamarlo como quieras.
  • Si solo está utilizando una ranura predeterminada, puede omitir esa etiqueta de template interna y colocar la directiva v-slot directamente en la etiqueta current-user .
  • Puede utilizar la desestructuración de objetos para crear referencias directas a los datos de la ranura del ámbito en lugar de utilizar un solo nombre de variable. En otras palabras, puede usar v-slot="{user}" en lugar de v-slot="slotProps" y luego puede usar user directamente en lugar de slotProps.user .

Teniendo en cuenta esas notas, el ejemplo anterior se puede reescribir así:

 // app.vue <template> <current-user v-slot="{user}"> {{ user.firstName }} </current-user> </template>

Un par de cosas más a tener en cuenta:

  • Puede enlazar más de un valor con directivas v-bind . Entonces, en el ejemplo, podría haber hecho más que solo user .
  • También puede pasar funciones a las ranuras con ámbito. Muchas bibliotecas usan esto para proporcionar componentes funcionales reutilizables, como verá más adelante.
  • v-slot tiene un alias de # . Entonces, en lugar de escribir v-slot:header="data" , puede escribir #header="data" . También puede simplemente especificar #header en lugar de v-slot:header cuando no esté utilizando ranuras con alcance. En cuanto a las ranuras predeterminadas, deberá especificar el nombre default cuando use el alias. En otras palabras, deberá escribir #default="data" en lugar de #="data" .

Hay algunos puntos menores más que puede aprender de los documentos, pero eso debería ser suficiente para ayudarlo a comprender de qué estamos hablando en el resto de este artículo.

¿Qué puedes hacer con las tragamonedas?

Las tragamonedas no se crearon con un solo propósito o, al menos, si lo fueran, han evolucionado mucho más allá de la intención original de ser una poderosa herramienta para hacer muchas cosas diferentes.

Patrones reutilizables

Los componentes siempre se diseñaron para poder reutilizarse, pero algunos patrones no son prácticos para aplicar con un solo componente "normal" porque la cantidad de props que necesitará para personalizarlo puede ser excesivo o necesitaría pasar grandes secciones de contenido y potencialmente otros componentes a través de los props . Las ranuras se pueden usar para abarcar la parte "exterior" del patrón y permitir que se coloquen otros HTML y/o componentes dentro de ellos para personalizar la parte "interior", permitiendo que el componente con ranuras defina el patrón y los componentes inyectados en el tragamonedas para ser único.

Para nuestro primer ejemplo, comencemos con algo simple: un botón. Imagina que tú y tu equipo estáis usando Bootstrap*. Con Bootstrap, sus botones a menudo se vinculan con la clase base `btn` y una clase que especifica el color, como `btn-primary`. También puede agregar una clase de tamaño, como `btn-lg`.

* Ni te animo ni te desanimo para que hagas esto, solo necesitaba algo para mi ejemplo y es bastante conocido.

Supongamos ahora, en aras de la simplicidad, que su aplicación/sitio siempre usa btn-primary y btn-lg . No querrás tener que escribir siempre las tres clases en tus botones, o tal vez no confíes en que un novato recuerde hacer las tres. En ese caso, puede crear un componente que tenga automáticamente las tres clases, pero ¿cómo permite la personalización del contenido? Un prop no es práctico porque una etiqueta de button puede tener todo tipo de HTML, por lo que debemos usar una ranura.

 <!-- my-button.vue --> <template> <button class="btn btn-primary btn-lg"> <slot>Click Me!</slot> </button> </template>

Ahora podemos usarlo en todas partes con el contenido que quieras:

 <!-- somewhere else, using my-button.vue --> <template> <my-button> <img src="/img/awesome-icon.jpg"> SMASH THIS BUTTON TO BECOME AWESOME FOR ONLY $500!!! </my-button> </template>

Por supuesto, puedes optar por algo mucho más grande que un botón. Siguiendo con Bootstrap, veamos un modal, o al menos la parte HTML; No voy a entrar en la funcionalidad... todavía.

 <!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <slot name="footer"></slot> </div> </div> </div> </div> </template>

Ahora, usemos esto:

 <!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <template #footer> <em>Now back to your regularly scheduled app usage</em> </template> </my-modal> </template>

El tipo de caso de uso anterior para tragamonedas es obviamente muy útil, pero puede hacer aún más.

Funcionalidad de reutilización

Los componentes de Vue no se tratan solo de HTML y CSS. Están construidos con JavaScript, por lo que también tienen que ver con la funcionalidad. Las ranuras pueden ser útiles para crear funciones una vez y usarlas en varios lugares. Volvamos a nuestro ejemplo modal y agreguemos una función que cierre el modal:

 <!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <!-- using `v-bind` shorthand to pass the `closeModal` method to the component that will be in this slot --> <slot name="footer" :closeModal="closeModal"></slot> </div> </div> </div> </div> </template> <script> export default { //... methods: { closeModal () { // Do what needs to be done to close the modal... and maybe remove it from the DOM } } } </script>

Ahora, cuando usa este componente, puede agregar un botón al pie de página que puede cerrar el modal. Normalmente, en el caso de un modal de Bootstrap, podría simplemente agregar data-dismiss="modal" a un botón, pero queremos ocultar cosas específicas de Bootstrap lejos de los componentes que se insertarán en este componente modal. Así que les pasamos una función a la que pueden llamar y no saben nada sobre la participación de Bootstrap:

 <!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <!-- pull in `closeModal` and use it in a button's click handler --> <template #footer="{closeModal}"> <button @click="closeModal"> Take me back to the app so I can be awesome </button> </template> </my-modal> </template>

Componentes sin procesamiento

Y finalmente, puede tomar lo que sabe sobre el uso de tragamonedas para pasar la funcionalidad reutilizable y eliminar prácticamente todo el HTML y solo usar las tragamonedas. Eso es esencialmente lo que es un componente sin procesamiento: un componente que proporciona solo funcionalidad sin ningún HTML.

Hacer que los componentes realmente no se rendericen puede ser un poco complicado porque necesitará escribir funciones de render en lugar de usar una plantilla para eliminar la necesidad de un elemento raíz, pero puede que no siempre sea necesario. Sin embargo, echemos un vistazo a un ejemplo simple que nos permite usar una plantilla primero:

 <template> <transition name="fade" v-bind="$attrs" v-on="$listeners"> <slot></slot> </transition> </template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>

Este es un ejemplo extraño de un componente sin procesamiento porque ni siquiera tiene JavaScript. Eso se debe principalmente a que solo estamos creando una versión reutilizable preconfigurada de una función integrada sin procesamiento: transition .

Sí, Vue tiene componentes integrados sin procesamiento. Este ejemplo en particular está tomado de un artículo sobre transiciones reutilizables de Cristi Jora y muestra una manera simple de crear un componente sin procesamiento que puede estandarizar las transiciones utilizadas en su aplicación. El artículo de Cristi profundiza mucho más y muestra algunas variaciones más avanzadas de las transiciones reutilizables, por lo que le recomiendo que lo consulte.

Para nuestro otro ejemplo, crearemos un componente que maneje cambiar lo que se muestra durante los diferentes estados de una Promesa: pendiente, resuelta con éxito y fallida. Es un patrón común y, si bien no requiere mucho código, puede enturbiar muchos de sus componentes si la lógica no se extrae para la reutilización.

 <!-- promised.vue --> <template> <span> <slot name="rejected" v-if="error" :error="error"></slot> <slot name="resolved" v-else-if="resolved" :data="data"></slot> <slot name="pending" v-else></slot> </span> </template> <script> export default { props: { promise: Promise }, data: () => ({ resolved: false, data: null, error: null }), watch: { promise: { handler (promise) { this.resolved = false this.error = null if (!promise) { this.data = null return } promise.then(data => { this.data = data this.resolved = true }) .catch(err => { this.error = err this.resolved = true }) }, immediate: true } } } </script>

Entonces, ¿qué está pasando aquí? Primero, tenga en cuenta que estamos recibiendo una propiedad llamada promise que es una Promise . En la sección de watch observamos los cambios en la promesa y cuando cambia (o inmediatamente en la creación del componente gracias a la propiedad immediate ) borramos el estado, llamamos then catch la promesa, actualizando el estado cuando finaliza con éxito o falla

Luego, en la plantilla, mostramos un espacio diferente según el estado. Tenga en cuenta que no pudimos mantenerlo realmente sin procesamiento porque necesitábamos un elemento raíz para usar una plantilla. También estamos pasando data y error a los ámbitos de ranura relevantes.

Y aquí hay un ejemplo de su uso:

 <template> <div> <promised :promise="somePromise"> <template #resolved="{ data }"> Resolved: {{ data }} </template> <template #rejected="{ error }"> Rejected: {{ error }} </template> <template #pending> Working on it... </template> </promised> </div> </template> ...

Pasamos somePromise al componente renderless. Mientras esperamos que termine, mostramos "Trabajando en ello..." gracias al espacio pending . Si tiene éxito, mostramos "Resuelto:" y el valor de resolución. Si falla, mostramos “Rechazado:” y el error que provocó el rechazo. Ahora ya no necesitamos rastrear el estado de la promesa dentro de este componente porque esa parte se extrae en su propio componente reutilizable.

Entonces, ¿qué podemos hacer con ese span que se envuelve alrededor de las ranuras en promised.vue ? Para eliminarlo, debemos eliminar la parte de la template y agregar una función de render a nuestro componente:

 render () { if (this.error) { return this.$scopedSlots['rejected']({error: this.error}) } if (this.resolved) { return this.$scopedSlots['resolved']({data: this.data}) } return this.$scopedSlots['pending']() }

No hay nada demasiado complicado pasando aquí. Solo usamos algunos bloques if para encontrar el estado y luego devolvemos la ranura de alcance correcta (a través this.$scopedSlots['SLOTNAME'](...) ) y pasamos los datos relevantes al alcance de la ranura. Cuando no esté usando una plantilla, puede omitir el uso de la extensión de archivo .vue extrayendo el JavaScript de la etiqueta del script y simplemente colocándolo en un archivo .js . Esto debería darle un ligero aumento de rendimiento al compilar esos archivos Vue.

Este ejemplo es una versión simplificada y ligeramente modificada de vue-promised, que recomendaría sobre el ejemplo anterior porque cubre algunos peligros potenciales. También hay muchos otros excelentes ejemplos de componentes sin procesamiento. Baleada es una biblioteca completa llena de componentes sin procesamiento que brindan una funcionalidad útil como esta. También hay vue-virtual-scroller para controlar la representación del elemento de la lista en función de lo que se ve en la pantalla o PortalVue para "teletransportar" contenido a partes completamente diferentes del DOM.

Estoy fuera

Las tragamonedas de Vue llevan el desarrollo basado en componentes a un nivel completamente nuevo y, aunque he demostrado muchas maneras excelentes en que se pueden usar las tragamonedas, hay muchas más. ¿Qué gran idea se te ocurre? ¿De qué maneras crees que las tragamonedas podrían obtener una actualización? Si tiene alguna, asegúrese de traer sus ideas al equipo de Vue. Dios los bendiga y feliz codificación.