5 dicas de arquitetura para criar componentes mais flexíveis

Gabriel Colombo
Minuto Frontend
Published in
7 min readMay 23, 2018
Imagem por Hal Gatewood

Nesse artigo eu gostaria de compartilhar com vocês algumas dicas que utilizamos em nossa biblioteca interna para criar componentes de UI mais flexíveis e reutilizáveis.

Os exemplos abaixo utilizam terminologias provenientes do Ember.js, porém os conceitos são agnósticos de framework. O código contém informação suficiente para demonstrar as ideias expostas nesse artigo.

Objetivos

De maneira resumida, os requisitos que a biblioteca deve atender são os seguintes:

  • Produtividade

Permitir com que as equipes consigam desenvolver de maneira ágil interfaces com um alto nível de customização, minimizando reescrita de código.

  • Fácil manutenção

Permitir realizar correções e evoluir as estruturas fornecidas pela biblioteca sem muitos efeitos colaterais.

  • Consistência

Garantir que as estruturas possuam o mesmo visual e comportamento quando utilizadas em contextos similares, adequando-se apropriadamente à mudanças no estado da aplicação.

Abordagens

Minimizar regras de negócio

Um dos problemas mais comuns são componentes que contém regras de negócio excessivas, fazendo com que o mesmo realize tarefas que, teoricamente, estão fora do seu escopo.

Antes de implementar qualquer funcionalidade, é importante listar algumas tarefas que o componente deve ser responsável.

Imagine que estamos criando um componente de botão. Ao utilizar esse componente, eu gostaria de poder realizar as seguintes tarefas:

  • Informar qual o tipo do botão — primário ou normal
  • Informar o conteúdo do botão — ícones e texto
  • Desabilitar e habilitar o botão
  • Realizar uma ação ao clicar no botão

Tendo essa pequena listagem das tarefas, vamos identificar as diferentes partes envolvidas no processo de criação do componente e ver onde cada parte pode ser alocada.

1—O conteúdo do botão é específico de cada componente, podendo ser alocado dentro de seu próprio arquivo.

O tipo do botão é um parâmetro obrigatório, então adicionaremos uma validação caso não seja informado.

Mapear propriedades internas em um objeto favorece a escalabilidade do componente

Mapear propriedades em um objeto favorece a escalabilidade do componente, caso seja necessário implementar outros estados.

2 — Vários componentes como botões e inputs possuem um estado desabilitado. As rotinas responsáveis por lidar com esse comportamento podem ser movidas para outro módulo ou uma estrutura compartilhada, evitando repetição — nós chamamos de mixin.

3 — O evento de clique também pode ser encontrado em múltiplos componentes, podendo assim ser movido para outro módulo. É importante reforçar que essa ação não deve conter nenhuma regra de negócio — somente executar o callback informado pelo desenvolvedor ao utilizar o componente.

Dessa maneira, podemos ter uma ideia dos diferentes cenários em que o componente pode ser utilizado, o que acaba contribuindo para a idealização de uma arquitetura base que beneficie sua evolução.

Separar estados reutilizáveis

Certas interações são comuns entre vários componentes, como:

  • Habilitar / Desabilitar— botões, inputs
  • Expandir / Recolher — collapse, dropdowns
  • Exibir / Esconder — qualquer componente

Essas propriedades são geralmente utilizadas para controlar estados visuais. Sendo assim, toda a lógica relativa à esses estados pode ser movida para um mixin.

Com isso é possível reutilizar essas rotinas em lugares diferentes e manter uma nomenclatura padronizada para cada estado.

Rotinas para controlar o estado habilitado / desabilitado dos componentes

Cada rotina é responsável somente por alternar o valor de uma variável e retornar o contexto do mixin para possibilitar chaining das operações, por exemplo:

Exemplo de chaining das rotinas do componente

Essa abordagem pode ser incrementada com o surgimento de novas necessidades. Por exemplo, a rotina pode ser refatorada para aceitar um contexto como parâmetro e controlar tanto variáveis internas quanto externas, tornando-se mais flexível.

Rotina indicando qual variável utilizar, externa ou interna

Abstrair funcionalidades básicas

Todo componente, independente do seu propósito, contém rotinas que devem sempre ser realizadas para garantir seu funcionamento, como verificar um callback antes de executá-lo.

Tais rotinas também podem ser movidas para um mixin próprio e reutilizadas dentro dos componentes, por exemplo:

Abstração para verificar se o callback informado é válido

Isso faz com que sua arquitetura se mantenha consistente, expansível e fácil de integrar com softwares e bibliotecas de terceiros.

Composição de componentes

Evite ao máximo reescrever funcionalidades. É possível criar componentes mais especializados agrupando componentes menores e sobrepondo métodos para satisfazer outras necessidades.

Por exemplo:

Componentes base: Botão, dropdown, input.Botão com dropdown => botão + dropdown
Autocomplete => input + dropdown
Select => input (readonly) + dropdown

Dessa maneira, cada componente é responsável por suas próprias tarefas, parametrização e tratamento de estado, enquanto o componente externo cuida da comunicação entre eles e das regras de negócio.

Separar responsabilidades

Ao compor componentes mais complexos, temos como prática separar os parâmetros informados entre os componentes internos.

Imagine que temos de criar um componente de select:

Declaração do componente de select

Internamente temos dois componentes — um input e um dropdown.

Implementação interna do componente de select

O papel principal do componente é apresentar a descrição do item selecionado para o usuário, porém ela não tem valor nenhum para a aplicação mas sim o valor do item.

Quando uma opção é selecionada, nós separamos o objeto, enviando a descrição para o input através de uma variável interna, enquanto o valor é enviado para a variável atrelada ao componente, atualizando o controller.

Esse conceito pode ser aplicado em componentes nos quais a variável atrelada deve ser transformada, como um número, autocomplete ou select.

Datepickers também podem implementar esse comportamento, removendo a máscara do valor para utilizar internamente, porém exibindo o valor mascarado para o usuário.

Os riscos dessa abordagem aumentam conforme as transformações se tornam mais complexas, seja por regras de negócio excessivas ou ter que disparar eventos conforme os valores são alterados, então recomendamos que seja feito um planejamento mais detalhado ao implementar esse conceito.

Presets vs Componentes novos

Conforme novos projetos são iniciados pode surgir a necessidade de otimizar componentes e serviços para facilitar o processo de desenvolvimento.

Essas otimizações podem ser realizadas através de presets ou componentes novos.

Presets são parâmetros. Quando informados, eles setam valores pré-definidos no componente, simplificando sua utilização. Componentes novos são, geralmente, versões mais especializadas dos componentes base.

A parte mais complexa é definir quando utilizar presets ou criar componentes novos. Nós utilizamos as seguintes diretrizes para auxiliar nessa decisão:

Quando utilizar presets

1 — Padrões de uso repetitivos

Existem situações na qual um componente é reutilizado em vários locais diferentes com a mesma parametrização. Nesses casos, é viável favorecer a utilização de presets ao invés de criar novos componentes, principalmente quando o componente base tem uma gama excessiva de parâmetros.

Exemplo de preset para um autocomplete de produtos

Os valores derivados do preset somente são setados no componente caso não tenham sido informados, mantendo sua flexibilidade.

Implementação simplificada de um módulo de presets

Essa abordagem reduz o conhecimento necessário para customizar o componente e facilita a manutenção do mesmo, já que os parâmetros padrão estão definidos em um único local.

2 — Componente base é muito complexo

Quando o componente base utilizado para criar outros componentes contém muitos parâmetros, criar novos componentes pode acarretar certos problemas, como:

  • Será necessário repassar a maioria —se não todos — os parâmetros do novo componente para os componentes base. Conforme novos componentes derivam a partir dele, quaisquer mudanças no componente base acarretaria uma quantidade enorme de atualizações para manter a compatibilidade, consequentemente, aumentando a incidência de bugs.
  • Conforme mais componentes são criados, maior a dificuldade de documentar e memorizar as diferentes nuâncias de cada um deles, principalmente para novos desenvolvedores.

Quando criar componentes novos

1 — Extender funcionalidades

Quando for necessário extender funcionalidades, é viável criar novos componentes. Isso evita o vazamento de lógica específica de uma funcionalidade para outros componentes.

Implementação interna de um componente de botão com dropdown

O exemplo acima utiliza o componente de botão como base, e extende seu layout para acomodar um ícone referente ao dropdown, juntamente com o componente de dropdown e seu estado atual de visibilidade.

2 — Decorando parâmetros

Uma outra motivação para criar novos componentes é a necessidade de decorar valores padrão ou controlar a disponibilidade de parâmetros.

Extendendo comportamento do método onFocus internamente

No exemplo acima, é atribuído ao parâmetro onFocus um callback interno que seleciona o valor do campo e executa o callback informado para o componente pelo desenvolvedor.

Nesse caso, somente o parâmetro onFocus é repassado para o componente de input. Isso ajuda a controlar o escopo de certas funcionalidades e evita validações desnecessárias.

Além disso, o evento onBlur foi substituído por outro evento — onChange, executado quando o usuário informa uma data no input ou seleciona através do calendário.

Conclusão

Ao criar seus componentes, procure manter um equilíbrio entre arquitetura e utilização, facilitando a vida dos desenvolvedores.

O melhor resultado surge quando todos no grupo fazem o melhor para si mesmo e para o grupo — John Nash

Não tenha vergonha em pedir opiniões. Sempre é possível encontrar algo que possa ser melhorado.

Para aprimorar suas habilidades em engenharia de software, eu recomendo a série “Composing Software”, do Eric Elliott.

Bom, espero que tenham gostado do artigo! Aplique esses conceitos no seu dia-a-dia, encontre novas abordagens e compartilhe conosco!

Fique à vontade para entrar em contato comigo no twitter — @gcolombo_.

Obrigado!

Gostou do artigo? Compartilhe sua opinião conosco!

Não esqueça de seguir nossa página para receber todas as novidades :)

--

--

Gabriel Colombo
Minuto Frontend

Front-end Developer - Writing about UX, Psychology & Web Development. Find me @gcolombo_