¬ŅEl dise√Īador tiene una nueva idea? ¬ŅQu√© podr√≠a ser m√°s f√°cil?

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> /* scoped styles */ </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:

  1. 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.
  2. Además, cuando se presiona un botón, debe "golpearse" en un indicador de progreso redondo.
  3. 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.
  4. 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).
  5. 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' // loading, success, error }; } }; </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 completo

Si 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 / s

De 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!

Source: https://habr.com/ru/post/438834/


All Articles