Una vez, un amigo se puso en contacto conmigo para preguntarme si tenía una forma de cambiar dinámicamente un elemento HTML a otro dentro del bloque de plantilla de Vue. Por ejemplo, cambiar un <div>
elemento a un <span>
elemento basado en algunos criterios. El truco consistía en hacer esto sin depender de una serie de v-if
y v-else
código.
No pensé mucho en eso porque no veía una razón sólida para hacer tal cosa; simplemente no aparece tan a menudo. Más tarde ese mismo día, sin embargo, se acercó de nuevo 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 un elemento dinámico de la manera que él necesitaba.
Esta pequeña característica puede mantener el código en la plantilla limpio y ordenado. Puede reducir v-if
y v-else
se reduce a una cantidad menor de código que es más fácil de entender y mantener. Esto nos permite usar métodos o métodos computados 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 en el que trabajo. De acuerdo, no es una gran característica 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.
Vue integrado <component>
elemento
Hay varias funciones disponibles en Vue que permiten cambios dinámicos fáciles en la vista. Una de esas características, la función <component>
elemento, permite que los componentes sean dinámicos y se enciendan bajo demanda. Tanto en la documentación de Vue 2 como en la de Vue 3, hay una pequeña nota sobre el uso de este elemento con elementos HTML; esa es la parte que ahora exploraremos.
La idea es aprovechar este aspecto de la <component>
elemento para intercambiar elementos HTML comunes que son algo similares en naturaleza; pero con diferentes funcionalidades, semánticas o visuales. 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, está destinado a 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 se necesitan ambos.
Los enlaces a menudo se diseñan como botones visualmente, al igual que Bootstrap .btn
clase que crea una apariencia similar a un botón. Con eso en mente, podemos crear fácilmente un componente que cambie entre los dos elementos en función de una sola propiedad. El componente será un botón por defecto, pero si un href
prop, se representará como un enlace.
Aquí está el <component>
en la plantilla:
<component
:is="element"
:href="https://css-tricks.com/dynamically-switching-from-one-html-element-to-another-in-vue/href"
class="my-button"
>
<slot />
</component>
Este límite is
puntos de atributo a un método calculado llamado element
y el límite href
atributo utiliza el bien llamado href
apuntalar. Esto aprovecha el comportamiento normal de Vue de que el atributo enlazado no aparece en el elemento HTML renderizado si la propiedad 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 href
apuntalar,. luego un <a>
se aplica el elemento; de lo contrario obtenemos un <button>
.
<my-button>this is a button</my-button>
<my-button href="https://www.css-tricks.com">this is a link</my-button>
El HTML se representa así:
<button class="my-button">this is a button</button>
<a href="https://www.css-tricks.com" class="my-button">this is a link</a>
En este caso, podría haber una expectativa de que estos dos sean similares visualmente, pero por necesidades semánticas y de accesibilidad, son claramente diferentes. Dicho esto, no hay razón para que los dos elementos generados tengan que tener el mismo estilo. Puedes usar el elemento con el selector div.my-button
en el bloque de estilo, o cree una clase dinámica que cambiará según el elemento.
El objetivo general es simplificar las cosas al permitir que un componente se represente potencialmente como dos elementos HTML diferentes según sea necesario, sin v-if
o v-else
!
¿Lista ordenada o desordenada?
Una idea similar a la del ejemplo de botón anterior, podemos tener un componente que genere diferentes elementos de lista. Dado que una lista desordenada y una lista ordenada utilizan el mismo elemento de lista (<li>
) elementos como niños, entonces eso es bastante fácil; solo intercambiamos <ul>
y <ol>
. Incluso si quisiéramos tener una opción para tener una lista de descripción, <dl>
, esto se logra fácilmente ya que el contenido es solo un espacio que puede aceptar <li>
elementos o <dt>
/<dd>
combinaciones.
El código de la plantilla es muy similar al del ejemplo del botón:
<component
:is="element"
class="my-list"
>
<slot>No list items!</slot>
</component>
Tenga en cuenta el contenido predeterminado dentro del elemento de la ranura, llegaré a eso en un momento.
Hay un apoyo para el tipo de lista que se utilizará que por defecto 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 existe la ranura predeterminada, lo que significa que tiene contenido para representar. Si es así, entonces el tipo de lista pasó a través del listType
se utiliza el apoyo. De lo contrario, el elemento se convierte en un <div>
que mostraría el mensaje “¡No hay elementos en la lista!” mensaje dentro del elemento de la ranura. De esta manera, si no hay elementos en la lista, el HTML no se representará 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 válidos aparentes. Otra cosa a considerar es la posible confusión de las herramientas de accesibilidad, lo que sugiere que se trata de una lista con un elemento que simplemente indica que no hay elementos.
Al igual que en el ejemplo de botón anterior, también puede diseñar cada lista de manera diferente. Esto podría basarse en selectores que apuntan al elemento con el nombre de la 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"
:class="`my-list__${element}`"
>
<slot>No list items!</slot>
</component>
El uso es tan simple como el ejemplo de botón anterior:
<my-list>
<li>list item 1</li>
</my-list>
<my-list list-type="ol">
<li>list item 1</li>
</my-list>
<my-list list-type="dl">
<dt>Item 1</dt>
<dd>This is item one.</dd>
</my-list>
<my-list></my-list>
Cada instancia representa el elemento de lista especificado. El último, sin embargo, resulta en un <div>
indicando que no hay elementos de la lista porque, bueno, ¡no hay una 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 ser beneficioso mantener listas contenidas en un componente por razones de estilo y facilidad de mantenimiento, se podrían considerar otras razones. Tomemos, por ejemplo, si algunas formas de funcionalidad estuvieran vinculadas a los diferentes tipos de listas. Tal vez considere una clasificación de un <ul>
lista que cambia a una <ol>
para mostrar el orden de clasificación y luego volver a cambiar cuando haya terminado?
Ahora estamos controlando los elementos.
Aunque estos dos ejemplos esencialmente están cambiando el componente del elemento raíz, considere profundizar en un componente. Por ejemplo, un título que podría necesitar cambiar de un <h2>
a una <h3>
basado en algunos criterios.
Si tiene que utilizar soluciones ternarias para controlar cosas más allá de unos pocos atributos, le sugiero que se quede con el v-if
. Tener que escribir más código para manejar atributos, clases y propiedades solo complica el código más que el v-if
. En esos casos, el v-if
hace que el código sea más simple a largo plazo y el código más simple es más fácil de leer y mantener.
Al crear un componente y hay un simple v-if
para cambiar entre elementos, considere este pequeño aspecto de una característica importante de Vue.
Ampliando la idea, un sistema de tarjetas flexible
Considere todo lo que hemos cubierto hasta ahora y utilícelo en un componente de tarjeta flexible. Este ejemplo de un componente de tarjeta permite colocar tres tipos diferentes de tarjetas en partes específicas del diseño de un artículo:
- Carta de héroe: Se espera que esto 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: Esto se usa como una línea de acciones del usuario antes o dentro del artículo.
- Tarjeta de información: Esto está destinado a las comillas de extracción.
Considere que cada uno de estos sigue un sistema de diseño y el componente controla el HTML para la semántica y el estilo.
En el ejemplo anterior, puede ver la tarjeta de héroe en la parte superior, una línea de tarjetas de llamado 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 class="custom-card__header" :style="bg">
<component :is="elements('header')" class="custom-card__header-content">
<slot name="header"></slot>
</component>
</header>
<div class="custom-card__content">
<slot name="content"></slot>
</div>
<footer class="custom-card__footer">
<component :is="elements('footer')" class="custom-card__footer-content" @click="footerClickHandler">
<slot name="footer"></slot>
</component>
</footer>
</component>
Hay tres de los elementos “componentes” en 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 está realizando la llamada.
El elements()
el 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 hay 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, inserté una clase en el elemento raíz de la tarjeta según el tipo de tarjeta que sea. Eso hace que sea bastante fácil crear el CSS para cada tipo de tarjeta según los requisitos. También puede 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 es menos probable que 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 cálculo simple colocado en el elemento de 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 background
prop, luego 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 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:header>Article Title</template>
<template v-slot:content>Lorem ipsum...</template>
<template v-slot:footer>January 1, 2011</template>
</custom-card>
Verá que cada sección de la tarjeta tiene su propia ranura para contenido. Y, para simplificar las cosas, el texto es lo único que se espera en las tragamonedas. 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 el uso del componente sea bastante simplista. Reemplaza la necesidad de tomar decisiones sobre la estructura HTML y, a su vez, la tarjeta simplemente se implementa.
A modo de comparación, aquí están los otros dos tipos que se utilizan en la demostración:
<custom-card type="cta">
<template v-slot:header>CTA Title One</template>
<template v-slot:content>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</template>
<template v-slot:footer>footer</template>
</custom-card>
<custom-card type="info">
<template v-slot:header>Here's a Quote</template>
<template v-slot:content>“Maecenas ... quis.”</template>
<template v-slot:footer>who said it</template>
</custom-card>
Nuevamente, observe que cada ranura solo espera texto ya que cada tipo de tarjeta genera sus propios elementos HTML según lo definido por el elements()
método. Si en el futuro se considera que se debe usar un elemento HTML diferente, es una simple cuestión de actualizar el componente. La incorporación de características para la accesibilidad es otra posible actualización futura. Incluso las funciones de interacción se pueden expandir, según los tipos de tarjeta.
La potencia está en el componente que está en el componente.
El nombre extraño <component>
El elemento en los componentes de Vue estaba destinado a una cosa, pero, como sucede a menudo, tiene un pequeño efecto secundario que lo hace bastante útil de otras maneras. El <component>
El elemento estaba destinado a cambiar dinámicamente los componentes de Vue dentro de otro componente a pedido. 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 cinturón de funciones de Vue que he usado. Espero que este artículo lleve adelante las ideas y la información sobre esta pequeña función para que explore cómo puede aprovechar esto en sus propios proyectos de Vue.