Hola
habrovchanin! Los diseñadores son personas ideológicas y clientes, especialmente con sus requisitos comerciales.
Imagina que has acumulado tu mejor UIkit del mundo en el% insert más genial de tu framework% JS. Parece que hay todo lo que el proyecto necesita. Ahora puede tomar café y cerrar todas las tareas nuevas lanzando componentes en la página. Aún mejor, si encontró un UIkit
en un vertedero de basura en espacios abiertos de NPM y coincide perfectamente con la UX / UI actual y sus necesidades. Ficcion!
Y realmente ... ¿a quién estoy bromeando? Es probable que tu felicidad sea de corta duración. Después de todo, cuando el diseñador viene corriendo con el Talmud de nuevas soluciones de interfaz de usuario para la siguiente página o "proyecto especial", algo saldrá mal de todos modos.
En este punto, ¿el desarrollador se enfrenta a la pregunta
"SECO o NO SECO" ? ¿De alguna manera debo personalizar los componentes existentes? Sí, para no retrasar la regresión en casos existentes. O actúe según el principio "funciona, no toque" y escriba nuevos componentes desde cero. Al mismo tiempo, inflar UIkit y complicar el soporte.
Si usted, como muchos, ha estado en tal situación, ¡mire debajo del corte!
A pesar de la extensa introducción, se me ocurrió la idea de escribir este artículo después de leer
uno de los hilos de comentarios sobre Habré. Allí, los muchachos fueron seriamente rociados sobre cómo personalizar el componente del botón en React. Bueno, después de ver un par de esos holivars en Telegram, la necesidad de escribir sobre esto finalmente se fortaleció.
Para empezar, intentemos imaginar qué "personalizaciones" podríamos necesitar aplicar al componente.
Estilos
En primer lugar, esta es la personalización de los estilos de componentes. Un ejemplo común es un botón gris, pero se necesita uno azul. O un botón sin esquinas redondeadas y de repente se necesitan. Basado en los holivars que leí, concluí que hay aproximadamente 3 enfoques para esto:
1. Estilos globales
Utiliza
todo el poder de los estilos CSS globales, bastante redirigido por
! Importante , de modo que fuera, globalmente, intente superponer los estilos del componente original. La decisión, por decirlo suavemente, es controvertida y demasiado directa. Además, tal opción simplemente no siempre es posible y al mismo tiempo viola desesperadamente cualquier encapsulación de estilos. A menos, por supuesto, que se use en sus componentes.
2. Pasando clases (estilos) desde el contexto padre
También una decisión bastante controvertida. Resulta que estamos creando un
accesorio especial, por ejemplo, lo llamaremos
clases y justo encima sumergimos las clases necesarias en el componente.
<Button classes="btn-red btn-rounded" />
Naturalmente, este enfoque solo funcionará si el componente admite la aplicación de estilos a su contenido de esta manera. Además, si el componente es un poco más complejo y consiste en una estructura anidada de elementos HTML, obviamente aplicar estilos a todos será problemático. Por lo tanto, se aplicarán al elemento raíz del componente y luego, utilizando las reglas CSS, de alguna manera se extenderán más. Tristemente
3. Configuración del componente usando accesorios
Parece la solución más sensata, pero al mismo tiempo menos flexible. En pocas palabras, esperamos que el autor del componente sea una especie de genio y haya pensado todas las opciones de antemano. Es decir, todo lo que podamos necesitar y determinar todos los accesorios necesarios para todos los resultados deseados:
<Button bgColor="red" rounded={true} />
Eso no suena muy probable, ¿eh? Quizás
Comportamiento

Aquí aún es más ambiguo. En primer lugar, porque las dificultades para personalizar el comportamiento de un componente provienen de la tarea. Cuanto más complejo es el componente y la lógica en él, y cuanto más complejo es el cambio que queremos hacer, más difícil es hacer ese cambio. Resultó algún tipo de tautología ... En resumen, ¿entiendes? ;-)
Sin embargo, incluso aquí, hay un conjunto de herramientas que nos ayudan a personalizar el componente o no. Dado que estamos hablando específicamente sobre el enfoque de componentes, destacaría las siguientes herramientas útiles:
1. Trabajo conveniente con accesorios
En primer lugar, debe ser capaz de imitar un conjunto de accesorios de componentes sin tener que volver a describir este conjunto y convenientemente usarlos como proxy.
Además, si intentamos agregar algún comportamiento al componente, lo más probable es que necesitemos usar un conjunto adicional de accesorios que no sean necesarios para el componente original. Por lo tanto, es bueno poder cortar parte de los accesorios y transferir solo lo necesario al componente original. Al mismo tiempo, mantener todas las propiedades sincronizadas.
La otra cara es cuando queremos implementar un caso especial de comportamiento de componentes. De alguna manera arreglar parte de su estado en una tarea específica.
2. Seguimiento del ciclo de vida del componente y eventos
En otras palabras, todo lo que sucede dentro de un componente no debe ser un libro completamente cerrado. De lo contrario, realmente complica la personalización de su comportamiento.
No me refiero a la violación de la encapsulación y la interferencia incontrolada en el interior. El componente debe administrarse a través de su API pública (generalmente son accesorios y / o métodos). Pero todavía es necesario poder "descubrir" de alguna manera lo que está sucediendo dentro y seguir el cambio en su estado.
3. Gestión imperativa
Asumiremos que no te dije esto. Y, sin embargo, a veces, es bueno poder obtener una instancia de un componente y "tirar de las cuerdas". Es mejor evitar esto, pero en casos particularmente complejos, no puede prescindir de él.
Ok, más o menos resolvió la teoría. En general, todo es obvio, pero no todo está claro. Por lo tanto, vale la pena considerar al menos un caso real.
Caso
Mencioné anteriormente que la idea de escribir un artículo surgió debido al holivar sobre la personalización de un botón. Por lo tanto, pensé que sería simbólico resolver un caso así. Cambiar estúpidamente el color o redondear las esquinas sería demasiado fácil, así que traté de encontrar un caso un poco más complejo.
Imagine que tenemos un cierto componente del botón básico, que se utiliza en la cárcel de ubicaciones de aplicaciones. Además, implementa un comportamiento básico para todos los botones de la aplicación, así como un conjunto de estilos básicos encapsulados que, de vez en cuando, se sincronizan con las guías de la interfaz de usuario y todo eso.
Además, se hace necesario tener un componente adicional para el botón de envío al servidor (botón de envío), que, además de los cambios de estilo, requiere un comportamiento adicional. Por ejemplo, puede ser un dibujo del progreso del envío, así como una representación visual del resultado de esta acción, con éxito o sin éxito.
Puede verse más o menos así:
No es difícil adivinar que el botón de base se encuentra a la izquierda y que el botón de envío a la derecha está en un estado de finalización exitosa de la solicitud. Bueno, si el caso está claro, ¡comencemos!
Solución
Todavía no podía entender qué causó exactamente el holivar en la decisión sobre React. Aparentemente no es tan simple. Por lo tanto, no probaré suerte y
usaré la herramienta más familiar,
SvelteJS ,
un marco de desaparición de nueva generación que es casi perfecto para resolver
tales problemas .
Acordaremos de inmediato, no interferiremos de ninguna manera con el código del botón base. Suponemos que no fue escrito por nosotros en absoluto, y su código está cerrado para correcciones. En este caso, el componente del botón base se verá así:
Button.html <button {type} {name} {value} {disabled} {autofocus} on:click > <slot></slot> </button> <script> export default { data() { return { type: 'button', disabled: false, autofocus: false, value: '', name: '' }; } }; </script> <style> </style>
Y usado de esta manera:
<Button on:click="cancel()">Cancel</Button>
Tenga en cuenta que el componente del botón es realmente muy básico. No contiene absolutamente ningún elemento auxiliar o accesorio que pueda ayudar en la implementación de la versión extendida del componente. Este componente ni siquiera admite la transferencia de estilos por accesorios o al menos algún tipo de personalización incorporada, y todos los estilos están estrictamente aislados y no se filtran.
Crear sobre la base de este componente otro, con una funcionalidad mejorada, e incluso sin realizar cambios, puede parecer una tarea fácil. Pero no cuando usas
Svelte .
Ahora determinemos qué debería poder hacer el botón Enviar:
- En primer lugar, el texto del marco y del botón debe ser verde. Al pasar el mouse, el fondo también debe ser verde en lugar de gris oscuro.
- Además, cuando se presiona un botón, debe "golpearse" en un indicador de progreso redondo.
- Al finalizar el proceso (que se controla externamente), es necesario que el estado del botón se pueda cambiar a exitoso (éxito) o no exitoso (error). Al mismo tiempo, el botón del indicador debe convertirse en una insignia verde con un daw o una insignia roja con una cruz.
- También es necesario poder establecer el tiempo después del cual la insignia correspondiente se convertirá nuevamente en un botón en su estado original (inactivo).
- Y, por supuesto, debe hacer todo esto en la parte superior del botón base para guardar y aplicar todos los estilos y accesorios desde allí.
Fuh, no es una tarea fácil. Primero creemos un nuevo componente y envuélvalo con el botón base:
SubmitButton.html <Button> <slot></slot> </Button> <script> import Button from './Button.html'; export default { components: { Button } }; </script>
Si bien este es exactamente el mismo botón, solo que peor: ni siquiera sabe cómo usar accesorios de proxy. Esto no importa, volveremos a esto más tarde.
Estilizar
Mientras tanto, pensemos en cómo podemos diseñar un nuevo botón, es decir, cambiar los colores, de acuerdo con la tarea. Desafortunadamente, parece que no podemos usar ninguno de los enfoques descritos anteriormente.
Dado que los estilos están aislados dentro de un botón, pueden surgir problemas con los estilos globales. También es imposible obtener estilos en el interior: el botón básico simplemente no admite esta función. Además de personalizar la apariencia con la ayuda de accesorios. Además, nos gustaría que todos los estilos escritos para el nuevo botón también se encapsulen dentro de este botón y no se filtren.
La solución es increíblemente simple, pero solo si ya está usando
Svelte . Entonces, solo escribe los estilos para el nuevo botón:
<div class="submit"> <Button> <slot></slot> </Button> </div> ... <style> .submit :global(button) { border: 2px solid #1ECD97; color: #1ECD97; } .submit :global(button:hover) { background-color: #1ECD97; color: #fff; } </style>
Una de las notas clave de
Svelte : las cosas simples deben resolverse simplemente. El modificador especial
: global en esta versión generará CSS de tal manera que solo los botones dentro del bloque con la clase de
envío que se encuentran en este componente recibirán los estilos especificados.
Incluso si el marcado del mismo tipo aparece de repente en cualquier otro lugar de la aplicación:
<div class="submit"> <button>Button</button> </div>
Los estilos del componente
SubmitButton de ninguna manera se "filtran" allí.
Con este método,
Svelte facilita la personalización fácil de los estilos de componentes anidados, al tiempo que preserva la encapsulación de los estilos de ambos componentes.
Lanzamos utilería y arreglamos el comportamiento
Bueno, tratamos con el estilo casi al instante y sin ningún accesorio adicional y pasando clases CSS directamente. Ahora necesita proxy todos los accesorios del componente
Button a través del nuevo componente. Sin embargo, no quisiera describirlos nuevamente. Sin embargo, para empezar, decidamos qué propiedades tendrá el nuevo componente.
A juzgar por la tarea,
SubmitButton debe monitorear el estado y también dar la oportunidad de especificar el retraso de tiempo entre el cambio automático de un estado exitoso / error al inicial:
<script> ... export default { ... data() { return { delay: 1500, status: 'idle' </script>
Por lo tanto, nuestro nuevo botón tendrá 4 estados: descanso, descarga, éxito o error. Además, de manera predeterminada, los últimos 2 estados cambiarán automáticamente al estado inactivo después de 1,5 segundos.
Para lanzar todos los accesorios transmitidos al componente
Button , pero al mismo tiempo cortar el
estado y el
retraso que obviamente no son válidos para ello, escribiremos una propiedad calculada especial. Y después de eso, simplemente usamos el operador de
propagación para "
untar " los accesorios restantes en el componente incrustado. Además, dado que estamos haciendo exactamente el botón de envío, debemos corregir el tipo de botón para que no se pueda cambiar desde el exterior:
<div class="submit"> <Button {...attrs} type="submit"> <slot></slot> </Button> </div> <script> ... export default { ... computed: { attrs: data => { const { delay, status, ...attrs } = data; return attrs; } }, }; </script>
Bastante simple y elegante.
Como resultado, obtuvimos una versión completamente funcional del botón básico con estilos modificados. Es hora de implementar el nuevo comportamiento del botón.
Cambiamos y rastreamos el estado
Entonces, cuando hace clic en el botón
SubmitButton, no solo debemos descartar el evento para que el código de usuario pueda procesarlo (como se hace en
Button ), sino también implementar una lógica comercial adicional: establezca el estado de descarga. Para hacer esto, simplemente tome el evento desde el botón base en su propio controlador, haga lo que necesite y envíelo más lejos:
<div class="submit"> <Button {...attrs} type="submit" on:click="click(event)"> <slot></slot> </Button> </div> <script> ... export default { ... methods: { click(e) { this.set({ status: 'loading' }); this.fire('click', e); } }, }; </script>
Además, el componente principal de este botón, que controla el proceso de envío de datos, puede establecer el estado de envío correspondiente (
éxito / error ) a través de accesorios. Al mismo tiempo, el botón debe realizar un seguimiento de dicho cambio en el estado y, después de un tiempo especificado, cambia automáticamente el estado a
inactivo . Para hacer esto, use el
enlace de actualización del ciclo de vida :
<script> ... export default { ... onupdate({ current: { status, delay }, changed }) { if (changed.status && ['success', 'error'].includes(status)) { setTimeout(() => this.set({ status: 'idle' }), delay); } }, }; </script>
Toques finales
Hay 2 puntos más que no son obvios de la tarea y surgen durante la implementación. En primer lugar, para que la animación de las metamorfosis de los botones sea uniforme, tendrá que cambiar el botón con los estilos y no con ningún otro elemento. Para hacer esto, podemos usar lo mismo
: global , para que no haya problemas. Pero además, es necesario que el marcado dentro del botón esté oculto en todos los estados, excepto en
inactivo .
Vale la pena mencionar por separado que el marcado dentro del botón puede ser cualquiera y se lanza al componente original del botón base a través de ranuras anidadas. Sin embargo, aunque parezca amenazante, la solución es más que primitiva: solo necesita envolver la ranura dentro del nuevo componente en un elemento adicional y aplicarle los estilos necesarios:
<div class="submit"> <Button {...attrs} type="submit" on:click="click(event)"> <span><slot></slot></span> </Button> </div> ... <style> ... .submit span { transition: opacity 0.3s 0.1s; } .submit.loading span, .submit.success span, .submit.error span { opacity: 0; } ... </style>
Además, dado que el botón no se oculta de la página, sino que se transforma junto con los estados, sería bueno deshabilitarlo en el momento del envío. En otras palabras, si el botón de envío se configuró en
deshabilitado usando accesorios, o si el estado no está
inactivo , debe deshabilitar el botón. Para resolver este problema, escribimos otra pequeña propiedad calculada
isDisabled y la aplicamos al componente anidado:
<div class="submit"> <Button {...attrs} type="submit" disabled={isDisabled}> <span><slot></slot></span> </Button> </div> <script> ... export default { ... computed: { ... isDisabled: ({ status, disabled }) => disabled || status !== 'idle' }, }; </script>
Todo estaría bien, pero el botón básico tiene un estilo que lo hace translúcido en el estado desactivado, pero no lo necesitamos si el botón solo se desactiva temporalmente debido a un cambio en el estado. De todos modos viene al rescate
: global :
.submit.loading :global(button[disabled]), .submit.success :global(button[disabled]), .submit.error :global(button[disabled]) { opacity: 1; }
Eso es todo! ¡El nuevo botón es hermoso y está listo para usar!
Omitiré intencionalmente detalles de la implementación de animaciones y todo esto. No solo porque no está directamente relacionado con el tema del artículo, sino también porque en esta parte la demostración no resultó como quisiéramos. No compliqué mi tarea e implementé una solución completamente llave en mano para tal botón y porté estúpidamente un ejemplo encontrado en Internet.
Por lo tanto, no aconsejo usar esta implementación en el trabajo. Recuerde, esto es solo una demostración de este artículo.
→
Demo interactiva y código de ejemplo completoSi le gustó el artículo y quería aprender más sobre
Svelte ,
lea otros
artículos . Por ejemplo,
"Cómo hacer una búsqueda de usuario en GitHub sin React + RxJS 6 + Recompose" . Escuche el podcast de Año Nuevo
RadioJS # 54 , donde hablé con cierto detalle sobre qué
es Svelte , cómo "desaparece" y por qué no es "otro marco js más".
Echa un vistazo al canal de telegramas en ruso
SvelteJS . ¡Ya somos más de doscientos de nosotros y estaremos encantados de verte!
P / sDe repente, las pautas de UI cambiaron. Ahora las etiquetas en todos los botones de la aplicación deben estar en mayúsculas. Sin embargo, no tenemos miedo de tal giro de los acontecimientos. Añadir
transformación de texto: mayúscula; en los estilos del botón base y continuar tomando café.
¡Que tengas un buen día de trabajo!