Olá,
habrovchanin! Designers são pessoas ideológicas e clientes, especialmente com seus requisitos de negócios.
Imagine que você empilhou o seu melhor kit de interface do usuário no mundo, com a% melhor inserção de sua estrutura% JS. Parece que há tudo o que o projeto precisa. Agora você pode tomar café e fechar todas as novas tarefas lançando componentes na página. Melhor ainda, se você encontrou esse kit de interface
do usuário
em um depósito de lixo nos espaços abertos do NPM e ele combina perfeitamente com o UX / UI atual e suas necessidades. Ficção!
E realmente ... com quem estou brincando? Sua felicidade provavelmente terá vida curta. Afinal, quando o designer vem executando o Talmud de novas soluções de interface do usuário para a próxima página ou "projeto especial", algo vai dar errado de qualquer maneira.
Neste ponto, o desenvolvedor se depara com a pergunta
"DRY or not DRY" ? De alguma forma, devo personalizar os componentes existentes? Sim, para não atrasar a regressão nos casos existentes. Ou aja de acordo com o princípio "funciona - não toque" e escreva novos componentes do zero. Ao mesmo tempo, aumenta o UIkit e complica o suporte.
Se você, como muitos, esteve em tal situação, olhe por baixo!
Apesar da extensa introdução, a idéia de escrever este artigo veio a mim depois de ler
um dos tópicos de comentários sobre Habré. Lá, os caras ficaram seriamente interessados em como personalizar o componente do botão no React. Bem, depois de assistir alguns desses holívoros no Telegram, a necessidade de escrever sobre isso foi finalmente reforçada.
Para começar, vamos tentar imaginar quais “personalizações” precisaremos aplicar ao componente.
Estilos
Primeiro de tudo, essa é a personalização dos estilos de componentes. Um exemplo comum é um botão cinza, mas é necessário um azul. Ou um botão sem cantos arredondados e de repente eles são necessários. Com base nos holívoros que li, concluí que existem aproximadamente 3 abordagens para isso:
1. Estilos globais
Use
todo o poder dos estilos globais de CSS, bastante redirecionados por
! Important , para que, externamente, tente sobrepor os estilos do componente original. A decisão, para dizer o mínimo, é controversa e direta demais. Além disso, essa opção nem sempre é possível e, ao mesmo tempo, viola desesperadamente qualquer encapsulamento de estilos. A menos que, é claro, seja usado em seus componentes.
2. Passando classes (estilos) do contexto pai
Também uma decisão bastante controversa. Acontece que estamos criando um
suporte especial, por exemplo, vamos chamá-lo de
classes e logo acima imergimos as classes necessárias no componente.
<Button classes="btn-red btn-rounded" />
Naturalmente, essa abordagem funcionará apenas se o componente suportar a aplicação de estilos ao seu conteúdo dessa maneira. Além disso, se o componente for um pouco mais complexo e consistir em uma estrutura aninhada de elementos HTML, obviamente a aplicação de estilos a todos será problemática. Portanto, eles serão aplicados ao elemento raiz do componente e, usando as regras CSS, eles se espalharão de alguma forma. Infelizmente.
3. Configurando o componente usando adereços
Parece a solução mais sensata, mas ao mesmo tempo a menos flexível. Simplificando, esperamos que o autor do componente seja algum tipo de gênio e tenha pensado em todas as opções com antecedência. Ou seja, tudo o que precisamos e determinamos todos os acessórios necessários para todos os resultados desejados:
<Button bgColor="red" rounded={true} />
Isso não parece muito provável, não é? Talvez.
Comportamento

Aqui é ainda mais ambíguo. Primeiro de tudo, porque as dificuldades em personalizar o comportamento de um componente vêm da tarefa. Quanto mais complexo o componente e a lógica nele, e também mais complexa a mudança que queremos fazer, mais difícil é fazer essa alteração. Algum tipo de tautologia acabou ... Em suma, você entende! ;-)
No entanto, mesmo aqui, há um conjunto de ferramentas que nos ajudam a personalizar o componente ou não. Como estamos falando especificamente sobre a abordagem de componentes, eu destacaria as seguintes ferramentas úteis:
1. Trabalho conveniente com adereços
Antes de tudo, é necessário poder imitar um conjunto de adereços de componente sem ter que re-descrever esse conjunto e convenientemente procurá-los ainda mais.
Além disso, se tentarmos adicionar algum comportamento ao componente, provavelmente, precisaremos usar um conjunto adicional de adereços que não são necessários pelo componente original. Portanto, é bom poder cortar parte dos acessórios e transferir apenas o necessário para o componente original. Ao mesmo tempo, mantendo todas as propriedades sincronizadas.
O outro lado é quando queremos implementar um caso especial de comportamento de componentes. De alguma forma, consertar parte de seu estado em uma tarefa específica.
2. Rastreando o ciclo de vida e os eventos dos componentes
Em outras palavras, tudo o que acontece dentro de um componente não deve ser um livro completamente fechado. Caso contrário, isso realmente complica a personalização de seu comportamento.
Não quero dizer violação do encapsulamento e interferência não controlada no interior. O componente deve ser gerenciado por meio de sua API pública (geralmente são adereços e / ou métodos). Mas ser capaz de "descobrir" de alguma forma o que está acontecendo por dentro e acompanhar a mudança em seu estado ainda é necessário.
3. Gestão imperativa
Vamos supor que eu não contei isso a você. E, no entanto, às vezes, é bom poder obter uma instância de um componente e imperativamente "puxar as cordas". É melhor evitar isso, mas em casos particularmente complexos, você não pode ficar sem ele.
Ok, meio que resolvi a teoria. Em geral, tudo é óbvio, mas nem tudo é claro. Portanto, vale considerar pelo menos algum caso real.
Case
Mencionei acima que a idéia de escrever um artigo surgiu por causa do holivar sobre a personalização de um botão. Por isso, pensei que seria simbólico resolver esse caso. Mudar estupidamente de cor ou arredondar cantos seria muito fácil, então tentei criar um caso um pouco mais complexo.
Imagine que temos um determinado componente do botão básico, usado na prisão de locais de aplicativos. Além disso, implementa algum comportamento básico para todos os botões do aplicativo, bem como um conjunto de estilos básicos encapsulados que, de tempos em tempos, são sincronizados com os guias da interface do usuário e tudo mais.
Além disso, torna-se necessário ter um componente adicional para o botão enviar para o servidor (botão enviar), que, além das alterações de estilo, requer comportamento adicional. Por exemplo, pode ser um desenho do progresso do envio, bem como uma representação visual do resultado dessa ação - com ou sem êxito.
Pode ser algo como isto:
Não é difícil adivinhar que o botão base está localizado à esquerda e o botão enviar à direita está em um estado de conclusão bem-sucedida da solicitação. Bem, se o caso estiver claro - vamos começar!
Solução
Eu ainda não conseguia descobrir o que exatamente causou o holivar na decisão sobre o React. Aparentemente, não é tão simples. Portanto, não tentarei a sorte e usarei a ferramenta mais familiar -
SvelteJS -
uma estrutura de desaparecimento de nova geração que é quase perfeita para resolver
esses problemas .
Concordaremos imediatamente, não interferiremos de forma alguma com o código do botão base. Assumimos que não foi escrito por nós e seu código está fechado para correções. Nesse caso, o componente do botão base será mais ou menos assim:
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>
E usado desta maneira:
<Button on:click="cancel()">Cancel</Button>
Observe que o componente do botão é realmente muito básico. Ele não contém absolutamente nenhum elemento auxiliar ou suporte que possa ajudar na implementação da versão estendida do componente. Esse componente nem suporta a transferência de estilos por objetos ou pelo menos algum tipo de personalização integrada, e todos os estilos são estritamente isolados e não vazam.
Criar com base nesse componente outro, com funcionalidade aprimorada e mesmo sem fazer alterações, pode parecer uma tarefa fácil. Mas não quando você usa o
Svelte .
Agora vamos determinar o que o botão enviar deve ser capaz de fazer:
- Primeiro de tudo, o quadro e o texto do botão devem estar verdes. Ao passar o mouse, o fundo também deve ser verde em vez de cinza escuro.
- Além disso, quando um botão é pressionado, ele deve "bater" em um indicador de progresso redondo.
- Após a conclusão do processo (que é controlado externamente), é necessário que o status do botão possa ser alterado para bem-sucedido (sucesso) ou não-bem-sucedido (erro). Ao mesmo tempo, o botão do indicador deve se transformar em um crachá verde com um daw ou um crachá vermelho com uma cruz.
- Também é necessário poder definir o tempo após o qual o emblema correspondente se tornará novamente um botão em seu estado original (inativo).
- E, é claro, você precisa fazer tudo isso em cima do botão base, salvando e aplicando todos os estilos e acessórios a partir daí.
Fuh, não é uma tarefa fácil. Vamos primeiro criar um novo componente e envolvê-lo com o botão base:
SubmitButton.html <Button> <slot></slot> </Button> <script> import Button from './Button.html'; export default { components: { Button } }; </script>
Embora este seja exatamente o mesmo botão, apenas pior - ele nem sabe como adotar proxy. Isso não importa, voltaremos a isso mais tarde.
Estilizar
Enquanto isso, vamos pensar em como podemos estilizar um novo botão, ou seja, mudar as cores, de acordo com a tarefa. Infelizmente, parece que não podemos usar nenhuma das abordagens descritas acima.
Como os estilos são isolados dentro de um botão, podem ocorrer problemas com estilos globais. Também é impossível incluir estilos - o botão básico simplesmente não suporta esse recurso. Além de personalizar a aparência com a ajuda de adereços. Além disso, gostaríamos que todos os estilos escritos para o novo botão também fossem encapsulados dentro desse botão e não vazassem.
A solução é incrivelmente simples, mas apenas se você já estiver usando o
Svelte . Então, basta escrever os estilos para o novo botão:
<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>
Uma das notas principais do
Svelte - coisas simples devem ser resolvidas com simplicidade. O modificador especial
: global nesta versão gerará CSS de tal maneira que somente os botões dentro do bloco com a classe de
envio que estão neste componente receberão os estilos especificados.
Mesmo que a marcação do mesmo tipo apareça repentinamente em qualquer outro local do aplicativo:
<div class="submit"> <button>Button</button> </div>
Os estilos do componente
SubmitButton de forma alguma “vazam” lá.
Usando esse método, o
Svelte facilita a personalização fácil dos estilos dos componentes aninhados, preservando o encapsulamento dos estilos dos dois componentes.
Jogamos adereços e corrigimos o comportamento
Bem, lidamos com o estilo quase instantaneamente e sem adereços adicionais e passando diretamente as classes CSS. Agora você precisa fazer o proxy de todos os acessórios do componente
Button por meio do novo componente. No entanto, eu não gostaria de descrevê-los novamente. No entanto, para iniciantes, vamos decidir quais propriedades o novo componente terá.
A julgar pela tarefa, o
SubmitButton deve monitorar o status, além de oferecer uma oportunidade para especificar o tempo decorrido entre a alteração automática de um estado de sucesso / erro no estado inicial:
<script> ... export default { ... data() { return { delay: 1500, status: 'idle' </script>
Portanto, nosso novo botão terá 4 estados: descanso, download, sucesso ou erro. Além disso, por padrão, os dois últimos estados serão alterados automaticamente para o estado ocioso após 1,5 segundos.
Para lançar todos os objetos transmitidos no componente
Button , mas ao mesmo tempo cortar o
status e o
atraso obviamente inválidos, escreveremos uma propriedade calculada especial. Depois disso, simplesmente usamos o operador de
espalhamento para "manchar" os adereços restantes no componente incorporado. Além disso, como estamos fazendo exatamente o botão de envio, precisamos corrigir o tipo de botão para que não possa ser alterado externamente:
<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>
Muito simples e elegante.
Como resultado, obtivemos uma versão totalmente funcional do botão básico com estilos modificados. É hora de implementar o novo comportamento do botão.
Mudamos e acompanhamos o status
Portanto, quando você clica no botão
SubmitButton, não devemos apenas jogar o evento fora, para que o código do usuário possa processá-lo (como é feito no
Button ), mas também implementar uma lógica comercial adicional - defina o estado do download. Para fazer isso, basta pegar o evento no botão base em seu próprio manipulador, fazer o que você precisa e enviá-lo ainda mais:
<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>
Além disso, o componente pai deste botão, que controla o processo de envio de dados, pode definir o status de envio correspondente (
êxito / erro ) por meio de adereços. Ao mesmo tempo, o botão deve rastrear essa alteração no status e, após um tempo especificado, alterar automaticamente o estado para
ocioso . Para fazer isso, use o gancho de
atualização do 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 finais
Existem mais 2 pontos que não são óbvios na tarefa e surgem durante a implementação. Primeiramente, para que a animação das metamorfoses dos botões seja suave, você precisará alterar o próprio botão com os estilos, e não com algum outro elemento. Para fazer isso, podemos usar o mesmo
: global , para que não haja problemas. Além disso, é necessário que a marcação dentro do botão esteja oculta em todos os status, exceto
ocioso .
Vale ressaltar separadamente que a marcação dentro do botão pode ser qualquer e é lançada no componente original do botão base através de slots aninhados. No entanto, embora pareça ameaçador, a solução é mais do que primitiva - você só precisa envolver o slot dentro do novo componente em um elemento adicional e aplicar os estilos necessários:
<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>
Além disso, como o botão não se oculta da página, mas se transforma em conjunto com os status, seria bom desativá-lo no momento do envio. Em outras palavras, se o botão enviar foi definido como
desativado usando acessórios, ou se o status não estiver
ocioso , você deverá desativar o botão. Para resolver esse problema, escrevemos outra pequena propriedade computada
isDisabled e aplicamos ao componente aninhado:
<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>
Tudo ficaria bem, mas o botão básico tem um estilo que o torna translúcido no estado desativado, mas não precisamos disso se o botão estiver apenas temporariamente desativado devido a uma alteração no status. O mesmo acontece com o resgate
: global :
.submit.loading :global(button[disabled]), .submit.success :global(button[disabled]), .submit.error :global(button[disabled]) { opacity: 1; }
Isso é tudo! O novo botão é lindo e pronto para começar!
Omitirei intencionalmente detalhes da implementação de animações e tudo isso. Não apenas porque não está diretamente relacionado ao tópico do artigo, mas também porque nesta parte a demonstração não saiu como gostaríamos. Não compliquei minha tarefa e implementei uma solução completamente pronta para usar como um botão e, de maneira estúpida, mostrei um exemplo encontrado na Internet.
Portanto, não aconselho usar esta implementação no trabalho. Lembre-se, isso é apenas uma demonstração deste artigo.
→
Demonstração interativa e código de exemplo completoSe você gostou do artigo e quis saber mais sobre o
Svelte ,
leia outros
artigos . Por exemplo,
"Como fazer uma pesquisa de usuário no GitHub sem React + RxJS 6 + Recompose" . Ouça o podcast
RadioJS # 54 do Ano Novo, onde conversei com alguns detalhes sobre o que
é Svelte , como "desaparece" e por que não é "mais uma estrutura js".
Confira o canal de telegrama em russo
SvelteJS . Já existem mais de duzentos de nós e teremos o maior prazer em vê-lo!
P / sDe repente, as diretrizes da interface do usuário foram alteradas. Agora os rótulos em todos os botões do aplicativo devem estar em maiúsculas. No entanto, não temos medo dessa mudança de eventos. Adicionar
transformação de texto: maiúscula; nos estilos do botão base e continue tomando café.
Tenha um bom dia de trabalho!