Civil & Structural Engineering
Creating Reusable Ui Elements with the Prototype Pattern in Vue.js
Table of Contents
Comprender el patrón de prototipo
El patrón de prototipo es un patrón de diseño creacional que se centra en el concepto de clonar objetos existentes en lugar de construir nuevos desde cero. Es particularmente valioso cuando el costo de crear una nueva instancia de una clase es más caro que copiar uno existente, o cuando el sistema debe ser independiente de cómo se crean sus objetos. En JavaScript, los objetos pueden ser clonados fácilmente utilizando o el operador de difusión, haciendo el marco de prototipo.
En su núcleo, el patrón se basa en un prototipo de objeto que sirve como un modelo. Cualquier nuevo objeto creado a partir de este prototipo hereda sus propiedades y métodos. El nuevo objeto puede ser personalizado sin afectar al prototipo original. Este enfoque reduce la redundancia, fomenta la consistencia y simplifica el mantenimiento, cuando el prototipo cambia, todos los clones basados en él se actualizan automáticamente (en referencias en vivo de prototipo de cadena).
En el contexto del desarrollo de la interfaz de usuario, el patrón de prototipo permite a los equipos construir una biblioteca de elementos reutilizables que compartan el comportamiento común, el estilo y la lógica. En lugar de duplicar el código para componentes similares, los desarrolladores clonan un componente base y lo adaptan a necesidades específicas. Esto resulta en ciclos de desarrollo más rápidos y menos errores.
El patrón de prototipo en Vue.js
Los componentes Vue.js son esencialmente objetos JavaScript que definen una plantilla, datos, métodos, propiedades calculadas y ganchos de ciclo de vida. Debido a esta naturaleza orientada hacia el objeto, Vue soporta naturalmente la herencia basada en prototipos. El marco proporciona varios mecanismos para implementar el patrón de prototipo:
- Extensión completa a través de : La opción de Vue permite que un componente herede otro componente, fusionando opciones.
- Mixins: Una mezcla es un bloque reutilizable de opciones de componentes que se pueden fusionar en cualquier componente, clonando eficazmente el comportamiento a través de múltiples prototipos.
- Composables (Composition API): Con Vue 3, las funciones composibles permiten la reutilización lógica sin depender de la herencia de objetos, ofreciendo una forma más flexible de clonación de prototipos.
- ] clonación de componentes dinámicos: Utilizando ] o junto con o operadores de difusión, los desarrolladores pueden crear nuevas definiciones de componentes basadas en las existentes en tiempo de ejecución.
El patrón de prototipo se alinea bien con el sistema reactiva de Vue. Cuando clonas una definición de componente, el componente resultante todavía reacciona a los cambios de datos y eventos del ciclo de vida como se esperaba. Esto hace que sea sencillo construir una familia de componentes de la interfaz de usuario que compartan un conjunto de características principales pero difieren en apariencia o comportamiento.
Patrón Prototipo con API de Opciones
En Vue 2 y Vue 3 Options API, puede utilizar la opción para crear un componente infantil que hereda de un componente padre. El componente del niño puede anular cualquier opción (datos, métodos, computados, etc.) mientras mantiene el resto. Esta es la implementación más cercana del patrón de prototipo clásico.
// BaseButton.js
export default {
name: 'BaseButton',
props: {
label: { type: String, default: 'Click me' }
},
data() {
return {
isDisabled: false
}
},
methods: {
handleClick() {
this.$emit('click')
}
},
template: `<button :disabled="isDisabled" @click="handleClick" class="btn btn-primary">{{ label }}</button>`
}
// SecondaryButton.js
import BaseButton from './BaseButton'
export default {
extends: BaseButton,
data() {
return {
isDisabled: false
}
},
template: `<button :disabled="isDisabled" @click="handleClick" class="btn btn-secondary">{{ label }}</button>`
}
En este ejemplo, es un clon de con una plantilla modificada y clase CSS. Los propulsores, datos y métodos son heredados, pero pueden ser sobrescribidos.
Patrón Prototipo con la API de Composición
Vue 3's Composition API fomenta el uso de funciones composables en lugar de la herencia. Sin embargo, todavía puede implementar el concepto prototipo creando una función de fábrica que devuelve un objeto de definición de componente, luego se propaga en un nuevo componente. Este enfoque es más explícito y evita la fusión de opciones profundas.
// useButtonPrototype.js
import { ref } from 'vue'
export function useButtonPrototype() {
const isDisabled = ref(false)
function handleClick(emit) {
emit('click')
}
return { isDisabled, handleClick }
}
// BaseButton.vue
<script setup>
import { useButtonPrototype } from './useButtonPrototype'
const emit = defineEmits(['click'])
const { isDisabled, handleClick } = useButtonPrototype()
</template>
<template>
<button :disabled="isDisabled" @click="handleClick(emit)" class="btn btn-primary">
<slot />
</button>
</template>
// SecondaryButton.vue
<script setup>
import { useButtonPrototype } from './useButtonPrototype'
const emit = defineEmits(['click'])
const { isDisabled, handleClick } = useButtonPrototype()
// Add secondary logic here
</template>
<template>
<button :disabled="isDisabled" @click="handleClick(emit)" class="btn btn-secondary">
<slot />
</button>
</template>
Aquí, la lógica prototipo se extrae en un composable, y cada componente clona esa lógica. Este patrón es a menudo más sostenible que las cadenas de herencia profundas.
Creación de un componente de base reutilizable
El primer paso en la aplicación del patrón de prototipo en Vue es diseñar un componente base sólido. Este componente debe encapsular la estructura y el comportamiento comunes que todas las variantes compartirán. Para un elemento UI como un botón, que incluye:
- Un o envoltorio con atributos accesibles.
- Props for customization: , , , etc.
- Ranuras para proyección de contenidos.
- Emitentes de eventos para interacciones.
- Estilismo común a través de clases CSS o un sistema de diseño.
Un componente base bien diseñado actúa como prototipo. Debe ser lo suficientemente flexible para manejar la mayoría de los casos de uso sin ser demasiado genérico. Por ejemplo, un componente podría incluir un fondo, botón cercano, transición y ranura para contenido, al tiempo que permite que las variantes añadan encabezados, calzados o animaciones personalizadas.
<template>
<transition name="fade">
<div v-if="visible" class="dialog-overlay" @click.self="close">
<div class="dialog" :class="sizeClass">
<button class="dialog-close" @click="close">×</button>
<div class="dialog-body">
<slot />
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
props: {
visible: Boolean,
size: { type: String, default: 'medium' }
},
computed: {
sizeClass() { return `dialog--${this.size}` }
},
methods: {
close() { this.$emit('update:visible', false) }
}
}
</script>
Este diálogo de base puede ser clonado para crear diálogos de confirmación, diálogos de formulario o modales informativos al ampliar su funcionalidad.
Componentes de cierre y ampliación
Vue ofrece varias maneras de clonar y extender componentes. La opción es la más sencilla para pequeñas modificaciones. Sin embargo, ten cuidado con la herencia profunda: Vue fusiona opciones en un orden específico (opciones propias de usuario √≥ extends √≥n mezclas). Las propiedades o métodos de datos dominantes pueden llevar a comportamientos inesperados si no tienes cuidado.
Usando para Variantes Simples
// ConfirmDialog.js
import BaseDialog from './BaseDialog.vue'
export default {
extends: BaseDialog,
props: {
confirmText: { type: String, default: 'OK' },
cancelText: { type: String, default: 'Cancel' }
},
template: `
<BaseDialog v-bind="$props" v-on="$listeners">
<template #default>
<slot />
</template>
<template #footer>
<button @click="close">{{ cancelText }}</button>
<button @click="$emit('confirm')">{{ confirmText }}</button>
</template>
</BaseDialog>
`,
components: { BaseDialog }
}
Observe que usa tanto como un prototipo (a través de ) como un componente infantil en la plantilla. Este patrón le permite añadir una calzada con botones mientras reutiliza la superposición, transición y lógica cercana del diálogo base.
Cierre programático con (Vue 2) o (Vue 3)
Para escenarios dinámicos, puede clonar una definición de componente en tiempo de ejecución.
import { defineComponent, h } from 'vue'
import BaseButton from './BaseButton.vue'
function createButtonVariant(customProps) {
return defineComponent({
extends: BaseButton,
setup(props, { slots }) {
// Override or extend behavior
return () => h(BaseButton, { ...customProps, ...props }, slots)
}
})
}
Esta técnica es útil cuando se construye un registro de componentes que genera variantes basadas en objetos de configuración de un CMS o API.
Usando Ranuras para la Personalización
Las ranuras son una característica principal de Vue que se alinean hermosamente con el Patrón Prototipo. Al definir ranuras en el componente base, permite que los clones reemplazar o aumentar partes de la UI sin reescribir todo el componente. Las ranuras nombradas permiten múltiples puntos de personalización.
Por ejemplo, un componente podría tener ranuras para , , y . Un clon podría poblar el encabezado con un título, la ranura predeterminada con una imagen y descripción, y el pie con un botón. El prototipo base permanece sin cambios, pero el clon puede producir tarjetas totalmente diferentes.
Mezclas, Extensiones y Compositores: ¿Qué utilizar?
El Patrón Prototipo se puede implementar con mezclas, extensivos o composables. Aquí hay una comparación rápida:
- Mixins: Útil cuando quieres compartir el comportamiento a través de múltiples componentes no relacionados. Sin embargo, las mezclas pueden llevar a nombrar colisiones y fuente poco clara de propiedades. Son una forma de clonación de prototipos pero a menudo se desalientan en favor de los composables en Vue 3.
- Extender: Lo mejor para crear una jerarquía directa entre padres e hijos. Es intuitivo cuando tienes un prototipo base claro y una variante que hereda todas las opciones. La desventaja es que crea un acoplamiento estricto.
- Composables: El enfoque más flexible. Permiten extraer la lógica en funciones reutilizables que pueden ser compuestas en cualquier componente. Los compuestos no crean una cadena de prototipos; en cambio, clonan el comportamiento a través de la invocación de funciones. Esto es a menudo preferido para aplicaciones Vue modernas.
Para la mayoría de las bibliotecas de elementos de la UI, una combinación funciona mejor: utilizar un composable para la reutilización lógica, un componente base para plantilla y estilo, y luego clonar ese componente base para las variantes importando y envolviéndolo.
Ejemplo en el mundo real: construcción de un sistema de notificación reutilizable
Imagine que necesita mostrar notificaciones en su aplicación: éxito, error, advertencia e información. Cada variante comparte el mismo diseño (icon, mensaje, botón de cierre) pero difiere en color e icono. Utilizando el patrón de prototipo, puede crear un componente y luego clonarlo para cada tipo.
- Define con props para , , y ranuras para icono. Incluye lógica para auto-desistimiento y animación.
- Crear composable que gestiona una cola de notificaciones (similar a un prototipo de piscina).
- Crear componentes individuales de notificación como que se extiende o compone con un esquema de color verde y un icono de marca de verificación.
- Registrar estos componentes a nivel mundial o utilizar un componente dinámico basado en el tipo.
El beneficio: todas las notificaciones comparten los mismos atributos de lógica desestimada, transición y accesibilidad. Cambiar el componente base (por ejemplo, añadir el giro a la desestimación) actualiza automáticamente cada variante.
Beneficios y Limitaciones del Patrón Prototipo
Beneficios:
- Reutilización del proyecto: Reduce la duplicación entre los elementos de la interfaz de usuario.
- Consistencia: Todos los clones comparten el mismo comportamiento y estilo base, asegurando una experiencia de usuario uniforme.
- Facilidad de mantenimiento: Un solo cambio al prototipo se propaga a todos los descendientes (si se usa la herencia) o se puede propagar a través de los composables.
- Desarrollo rápido: Se pueden crear nuevas variantes mediante la clonación de un componente existente y la realización de cambios necesarios.
- Testabilidad: Las pruebas de prototipo de base cubren la funcionalidad común; sólo las pruebas de variante específica deben ser escritas.
Limitations:
- Overhead of cloning: En JavaScript, la clonación de objetos puede ser superficial o profunda. La clonación sin cuidado puede llevar a mutación involuntaria de referencias compartidas.
- Tight coupling in inheritance: Utilizar puede hacer variantes frágiles si el componente base cambia la estructura interna. El componente base debe diseñarse cuidadosamente para apoyar los puntos de extensión.
- La complejidad en las jerarquías profundas: Los múltiples niveles de herencia pueden ser difíciles de razonar. Preferir la composición sobre la herencia, cuando sea posible.
- Potencial para la duplicación en plantillas: Si los clones necesitan anular grandes partes de la plantilla, puede terminar copiando toda la plantilla en lugar de heredarla. Usando ranuras mitiga esto.
Comparación con el Patrón de Fábrica
El Patrón de Fábrica también crea objetos, pero separa la creación de objetos del código cliente. En cambio, el Patrón Prototipo se basa en la clonación de una instancia existente. En Vue, una función de fábrica podría devolver una nueva definición de componente basada en parámetros, mientras que un enfoque basado en prototipos comenzaría con un componente base y modificaría un clon. Ambas son útiles; las fábricas son a menudo mejores cuando usted necesita reunir componentes de múltiples fuentes, mientras que los prototipos de base sólidas se superen.
Las mejores prácticas para usar el patrón de prototipo en Vue
- Design prototipos con extensión en mente: Use props, ranuras y eventos de forma liberal. Evite los nombres de clase de codificación dura o estilos de línea que no pueden ser sobrescribidos.
- Prefer composables sobre mezclas] en Vue 3. Proporcionan dependencias explícitas y evitan el nombre de colisiones.
- Profundidad de herencia: No más de dos o tres niveles. Las cadenas profundas se vuelven difíciles de depurar.
- Documentar la interfaz prototipo: Esclarece claramente que se pretende sobreponer los propulsores, ranuras y métodos, lo que ayuda a otros desarrolladores a crear clones consistentes.
- Use TypeScript: Definir interfaces para opciones de componentes y propulsores hace que la clonación sea más segura y predecible.
- Evitar las instancias de componentes de clonación en tiempo de ejecución] si se puede utilizar definiciones de componentes estáticos. La clonación dinámica puede ser ineficiente y difícil de mantener.
- Prueba el prototipo independientemente. Luego prueba cada variante sólo para sus personalizaciones.
Avanzadas: Cierre de componentes programáticos con
En Vue 3, puede crear una fábrica que devuelve una nueva definición de componente mediante la clonación de una base y la aplicación de anulas. Esto es útil para crear bibliotecas UI altamente configurables que se utilizan en diferentes proyectos con diferentes sistemas de diseño.
import { defineComponent, h } from 'vue'
function createClonedComponent(base, overrides = {}) {
return defineComponent({
name: overrides.name || `Cloned${base.name}`,
extends: base,
setup(props, { slots, emit }) {
// Apply overrides logic
const merged = { ...base.setup?.(props, { slots, emit }), ...overrides.setup?.(props, { slots, emit }) }
return merged
}
})
}
const SuccessNotification = createClonedComponent(BaseNotification, {
name: 'SuccessNotification',
setup() {
// Add success-specific logic
const icon = 'check-circle'
return { icon }
}
})
Este enfoque le permite generar un kit completo de interfaz de usuario de un conjunto de prototipos y archivos de configuración.
Conclusión
El Patrón Prototipo es una herramienta poderosa en el arsenal del desarrollador Vue.js, permitiendo la creación de elementos de interfaz de usuario reutilizables que sean consistentes, sostenibles y fáciles de extender. Entendiendo cómo clonar componentes a través de , mezclas o composables, y diseñando componentes básicos con ranuras y props, puede construir una biblioteca de componentes flexible que se adapte a los cambios de los requisitos con un esfuerzo mínimo.
Para más lectura, explore Vue.js Composition API documentation] y Patterns.dev: Prototype Pattern. Además, compruebe las discusiones sobre Vue.extend vs mixins sobre el flujo de la población.