Cambiar dinámicamente de un elemento HTML a otro en Vue

Una vez, un amigo me contactó preguntándome si tenía una manera de cambiar dinámicamente un elemento HTML a otro dentro del bloque de plantilla de Vue. Por ejemplo, cambie un divelemento a spanotro según algunos criterios. El truco consistía en hacer esto sin depender de una serie de v-ifcódigos v-else.

No pensé mucho en ello porque no veía una razón fuerte para hacer tal cosa; simplemente no aparece tan a menudo. Sin embargo, más tarde, ese mismo día, se volvió a acercar y me dijo que había aprendido a cambiar los tipos de elementos. Señaló con entusiasmo que Vue tiene un componente incorporado que puede usarse como elemento dinámico de la manera que él necesita.

Esta pequeña característica puede mantener el código de la plantilla agradable y ordenado. Puede reducir v-ify v-elsesaturar a una cantidad más pequeña de código que es más fácil de entender y mantener. Esto nos permite utilizar métodos o métodos calculados para crear condiciones bien codificadas y aún más elaboradas en el bloque de script. Ahí es donde pertenecen esas cosas: en el script, no en el bloque de plantilla.

Tuve la idea de este artículo principalmente porque usamos esta función en varios lugares del sistema de diseño donde trabajo. Es cierto que no es una característica muy importante y apenas se menciona en la documentación, al menos hasta donde yo sé. Sin embargo, tiene potencial para ayudar a representar elementos HTML específicos en componentes.

componentElemento incorporado de vista

Hay varias funciones disponibles en Vue que permiten cambios dinámicos sencillos en la vista. Una de esas características, el elemento integrado component, permite que los componentes sean dinámicos y conmutados según la demanda. Tanto en la documentación de Vue 2 como de Vue 3, hay una pequeña nota sobre el uso de este elemento con elementos HTML; esa es la parte que exploraremos ahora.

La idea es aprovechar este aspecto del componentelemento para intercambiar elementos HTML comunes que sean algo similares en la naturaleza; pero con diferente funcionalidad, semántica o imágenes. Los siguientes ejemplos básicos mostrarán el potencial de este elemento para ayudar a mantener los componentes de Vue limpios y ordenados.

¿Botón o enlace?

Los botones y enlaces a menudo se usan indistintamente, pero existen grandes diferencias en su funcionalidad, semántica e incluso imágenes. En términos generales, un botón ( button) está destinado a una acción interna en la vista actual vinculada al código JavaScript. Un enlace ( a), por otro lado, pretende apuntar a otro recurso, ya sea en el servidor host o en un recurso externo; más a menudo páginas web. Las aplicaciones de una sola página tienden a depender más del botón que del enlace, pero ambos son necesarios.

Los enlaces a menudo tienen el estilo visual de botones, muy parecido a la clase de Bootstrap .btnque crea una apariencia similar a un botón. Con eso en mente, podemos crear fácilmente un componente que cambie entre los dos elementos basándose en un único accesorio. El componente será un botón de forma predeterminada, pero si hrefse aplica un accesorio, se representará como un enlace.

Aquí está el componentde la plantilla:

component  :is="element"  :href="href"   slot //component

Este isatributo vinculado apunta a un método calculado elementy el hrefatributo vinculado utiliza la propiedad denominada apropiadamente href. Esto aprovecha el comportamiento normal de Vue de que el atributo vinculado no aparece en el elemento HTML renderizado si el accesorio no tiene valor. La ranura proporciona el contenido interno independientemente de si el elemento final es un botón o un enlace.

El método calculado es de naturaleza simple:

element () {  return this.href ? 'a' : 'button';}

Si hay un hrefaccesorio. luego ase aplica un elemento; de lo contrario obtenemos un button.

my-buttonthis is a button/my-buttonmy-button href="https://www.css-tricks.com"this is a link/my-button

El HTML se representa así:

buttonthis is a button/buttona href="https://www.css-tricks.com"this is a link/a

En este caso, se podría esperar que estos dos sean similares visualmente, pero por necesidades semánticas y de accesibilidad, son claramente diferentes. Dicho esto, no hay ninguna razón por la que los dos elementos generados deban tener el mismo estilo. Puede usar el elemento con el selector div.my-buttonen el bloque de estilo o crear una clase dinámica que cambiará según el elemento.

El objetivo general es simplificar las cosas permitiendo que un componente se represente potencialmente como dos elementos HTML diferentes según sea necesario, ¡sin v-ifo v-else!

¿Lista ordenada o desordenada?

Una idea similar a la del ejemplo del botón anterior: podemos tener un componente que genere diferentes elementos de lista. Dado que una lista desordenada y una lista ordenada utilizan los mismos elementos de lista ( li) como elementos secundarios, entonces eso es bastante fácil; simplemente intercambiamos uly ol. Incluso si quisiéramos tener una opción para tener una lista de descripciones, dlesto se logra fácilmente ya que el contenido es solo una ranura que puede aceptar lielementos o dtcombinaciones dd.

El código de la plantilla es muy parecido al ejemplo del botón:

component  :is="element"   slotNo list items!/slot/component

Tenga en cuenta el contenido predeterminado dentro del elemento de ranura, llegará a eso en un momento.

Hay un accesorio para el tipo de lista que se utilizará y cuyo valor predeterminado es ul:

props: {  listType: {    type: String,    default: 'ul'  }}

Nuevamente, hay un método calculado llamado element:

element () {  if (this.$slots.default) {    return this.listType;  } else {    return 'div';  }}

En este caso, estamos probando si el espacio predeterminado existe, lo que significa que tiene contenido para representar. Si es así, entonces listTypese utiliza el tipo de lista pasado a través del accesorio. De lo contrario, el elemento se convierte en un divelemento que mostrará el mensaje “¡No hay elementos en la lista!”. mensaje dentro del elemento de ranura. De esta manera, si no hay elementos de la lista, el HTML no se mostrará como una lista con un elemento que diga que no hay elementos. Ese último aspecto depende de usted, aunque es bueno considerar la semántica de una lista sin elementos aparentemente válidos. Otra cosa a considerar es la posible confusión de las herramientas de accesibilidad que sugiere que se trata de una lista con un elemento que simplemente indica que no hay ningún elemento.

Al igual que en el ejemplo del botón anterior, también puedes diseñar cada lista de manera diferente. Esto podría basarse en selectores que punten al elemento con el nombre de clase ul.my-list. Otra opción es cambiar dinámicamente el nombre de la clase según el elemento elegido.

Este ejemplo sigue una estructura de nomenclatura de clases similar a BEM :

component  :is="element"   :class="`my-list__${element}`"  slotNo list items!/slot/component

El uso es tan simple como el ejemplo del botón anterior:

my-list  lilist item 1/li/my-listmy-list list-type="ol"  lilist item 1/li/my-listmy-list list-type="dl"  dtItem 1/dt  ddThis is item one./dd/my-listmy-list/my-list

Cada instancia representa el elemento de la lista especificada. El último, sin embargo, da como resultado que divno se indican elementos de la lista porque, bueno, ¡no hay ninguna lista para mostrar!

Uno podría preguntarse por qué crear un componente que cambie entre los diferentes tipos de listas cuando podría ser simplemente HTML. Si bien podría haber beneficios al mantener listas contenidas en un componente por razones de estilo y mantenibilidad, se podrían considerar otras razones. Tomemos, por ejemplo, si algunas formas de funcionalidad estarían vinculadas a los diferentes tipos de listas. ¿Quizás considerar ordenar una ullista que cambie aa olpara mostrar el orden de clasificación y luego volver a cambiar cuando haya terminado?

Ahora estamos controlando los elementos.

Aunque estos dos ejemplos esencialmente cambian el componente del elemento raíz, considere más profundamente un componente. Por ejemplo, un título que podría necesitar cambiar de h2a h3según algunos criterios.

Si tiene que utilizar soluciones ternarias para controlar cosas más allá de unos pocos atributos, le sugerimos que se quede con la solución v-if. Tener que escribir más código para manejar atributos, clases y propiedades simplemente complica el código más que el archivo v-if. En esos casos, esto v-ifhace que el código sea más simple a largo plazo y que el código más simple sea más fácil de leer y mantener.

Al crear un componente y es sencillo v-ifcambiar entre elementos, considere este pequeño aspecto de una característica importante de Vue.

Ampliando la idea, un sistema de tarjetas flexibles

Considere todo lo que hemos cubierto hasta ahora y úselo en un componente de tarjeta flexible. Este ejemplo de componente de tarjeta permite colocar tres tipos diferentes de tarjetas en partes específicas del diseño de un artículo:

  • Tarjeta de héroe: se espera que se use en la parte superior de la página y llame más la atención que otras tarjetas.
  • Tarjeta de llamada a la acción: se utiliza como una línea de acciones del usuario antes o dentro del artículo.
  • Tarjeta de información: está destinada a cotizaciones extraídas.

Considere que cada uno de estos sigue un sistema de diseño y el componente controla el HTML en cuanto a semántica y estilo.

En el ejemplo anterior, puede ver la tarjeta de héroe en la parte superior, una línea de tarjetas de llamada a la acción a continuación y luego, desplazándose un poco hacia abajo, verá la tarjeta de información en el lado derecho.

Aquí está el código de plantilla para el componente de la tarjeta:

component :is="elements('root')" :class="'custom-card custom-card__' + type" @click="rootClickHandler"  header :style="bg"    component :is="elements('header')"      slot name="header"/slot    /component  /header  div    slot name="content"/slot  /div  footer    component :is="elements('footer')" @click="footerClickHandler"      slot name="footer"/slot    /component  /footer/component

Hay tres de los elementos “componentes” de la tarjeta. Cada uno representa un elemento específico dentro de la tarjeta, pero se cambiará según el tipo de tarjeta que sea. Cada componente llama al elements()método con un parámetro que identifica qué sección de la tarjeta realiza la llamada.

El elements()método es:

elements(which) {  const tags = {    hero: { root: 'section', header: 'h1', footer: 'date' },    cta: { root: 'section', header: 'h2', footer: 'div' },    info: { root: 'aside', header: 'h3', footer: 'small' }  }  return tags[this.type][which];}

Probablemente haya varias formas de manejar esto, pero tendrá que ir en la dirección que funcione con los requisitos de su componente. En este caso, hay un objeto que realiza un seguimiento de las etiquetas de elementos HTML para cada sección en cada tipo de tarjeta. Luego, el método devuelve el elemento HTML necesario según el tipo de tarjeta actual y el parámetro pasado.

Para los estilos, inserte una clase en el elemento raíz de la tarjeta según el tipo de tarjeta que es. Eso hace que sea bastante fácil crear el CSS para cada tipo de tarjeta según los requisitos. También puedes crear el CSS basado en los propios elementos HTML, pero tiendo a preferir las clases. Los cambios futuros en el componente de la tarjeta podrían cambiar la estructura HTML y hacer menos probable que se realicen cambios en la lógica que crea la clase.

La tarjeta también admite una imagen de fondo en el encabezado de la tarjeta de héroe. Esto se hace con un simple calculado colocado en el elemento del encabezado: bg. Este es el calculado:

bg() {  return this.background ? `background-image: url(${this.background})` : null;}

Si se proporciona una URL de imagen en el backgroundaccesorio, entonces el cálculo devuelve una cadena para un estilo en línea que aplica la imagen como imagen de fondo. Una solución bastante simple que fácilmente podría hacerse más robusta. Por ejemplo, podría admitir colores personalizados, degradados o colores predeterminados en caso de que no se proporcione una imagen. Hay una gran cantidad de posibilidades que su ejemplo no aborda porque cada tipo de tarjeta podría tener sus propios accesorios opcionales para que los desarrolladores los aprovechen.

Aquí está la carta de héroe de esta demostración:

custom-card type="hero" background="https://picsum.photos/id/237/800/200"  template v-slot:headerArticle Title/template  template v-slot:contentLorem ipsum.../template  template v-slot:footerJanuary 1, 2011/template/custom-card

Verás que cada sección de la tarjeta tiene su propia ranura para contenido. Y, para simplificar las cosas, lo único que se espera en las ranuras es texto. El componente de tarjeta maneja el elemento HTML necesario basándose únicamente en el tipo de tarjeta. Hacer que el componente solo espere texto hace que su uso sea de naturaleza bastante simplista. Reemplaza la necesidad de tomar decisiones sobre la estructura HTML y, a su vez, la tarjeta se implementa de manera sencilla.

A modo de comparación, estos son los otros dos tipos que se utilizan en la demostración:

custom-card type="cta"  template v-slot:headerCTA Title One/template  template v-slot:contentLorem ipsum dolor sit amet, consectetur adipiscing elit./template  template v-slot:footerfooter/template/custom-cardcustom-card type="info"  template v-slot:headerHere's a Quote/template  template v-slot:content“Maecenas ... quis.”/template  template v-slot:footerwho said it/template/custom-card

Nuevamente, observe que cada espacio solo espera texto ya que cada tipo de tarjeta genera sus propios elementos HTML según lo define el elements()método. Si se considera que en el futuro se debe utilizar un elemento HTML diferente, es una simple cuestión de actualizar el componente. La incorporación de funciones de accesibilidad es otra posible actualización futura. Incluso las funciones de interacción se pueden ampliar según los tipos de tarjetas.

El poder está en el componente que está en el componente.

El componentelemento con un nombre extraño en los componentes de Vue estaba destinado a una cosa pero, como suele suceder, tiene un pequeño efecto secundario que lo hace bastante útil de otras maneras. El componentelemento estaba destinado a cambiar dinámicamente componentes de Vue dentro de otro componente según demanda. Una idea básica de esto podría ser un sistema de pestañas para cambiar entre componentes que actúan como páginas; que en realidad se demuestra en la documentación de Vue. Sin embargo, admite hacer lo mismo con elementos HTML.

Este es un ejemplo de una nueva técnica compartida por un amigo que se ha convertido en una herramienta sorprendentemente útil en el conjunto de funciones de Vue que he usado. Espero que este artículo transmita las ideas y la información sobre esta pequeña característica para que pueda explorar cómo puede aprovecharla en sus propios proyectos de Vue.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir