Alura > Cursos de Front-end > Cursos de React > Conteúdos de React > Primeiras aulas do curso SOLID aplicado ao React: melhorando a modularidade e flexibilidade do código

SOLID aplicado ao React: melhorando a modularidade e flexibilidade do código

Princípio da responsabilidade única - Apresentação

Olá, estudante da Alura, tudo bem? Meu nome é Patrícia Silva.

Audiodescrição: Patrícia Silva se descreve como uma mulher branca. Tem cabelos cacheados escuros na altura dos ombros e olhos castanhos. Usa óculos de aro preto e camiseta bordô. Ela está em um ambiente iluminado com cores azul e lilás.

Estamos felizes em te encontrar em mais um curso. Este curso é sobre SOLID aplicado ao React e teremos a oportunidade de aplicá-lo no desenvolvimento front-end.

O que vamos aprender?

Utilizaremos um e-commerce pré-existente chamado UseDev. Trata-se de uma aplicação funcional, mas ao longo do curso, passaremos por cada componente, aplicando cada princípio do SOLID para melhorar a estrutura e a manutenção do código.

Página inicial de website da marca UseDev com uma paleta de cores em tons de roxo, rosa e verde. No topo, cabeçalho com o logotipo da marca, botão 'Sobre nós', campo de pesquisa, botão de 'Login' e ícones de perfil e carrinho de compras. Logo abaixo, banner com a fotografia de uma pessoa com camiseta preta com símbolos de botões de controle de videogame coloridos e frase 'Hora de abraçar seu lado geek!' em destaque e botão 'Ver as novidades'. Abaixo, seção intitulada 'Categorias', com as categorias 'Roupas' e 'Decoração'.

A aplicação tem três páginas diferentes: a página home, que lista as categorias e os produtos em promoções; a página de detalhes do produto; e a página de carrinho de compras.

Com o SOLID aplicado ao React, perceberemos como podemos transformar este projeto em uma aplicação robusta, modular e flexível, além de mais fácil de testar e entender. Isso não apenas ajudará na manutenção do código, mas também na evolução das funcionalidades ao longo do ciclo de vida do projeto.

Pré-requisitos

Aplicar os princípios do SOLID não é apenas uma técnica, mas uma mentalidade. Para fazer tudo isso, é necessário ter conhecimento de React e TypeScript.

Vamos juntos transformar o UseDev em um projeto mais robusto, modular e escalável. Até já!

Princípio da responsabilidade única - Refatoração do botão

Já estamos com a aplicação UseDev aberta, clonada e rodando no navegador. Esta aplicação é um e-commerce que precisa de melhorias para escalar de forma fácil e barata. Se ela for escalável e robusta, será também mais econômica, pois conseguiremos colocar as funcionalidades em produção rapidamente.

Responsabilidade única

Vamos começar pela base, que são os botões, já que é um elemento tão comum. Na página inicial, temos um botão "Ver as novidades" no hero banner e um botão de "Inscrever" na seção de newsletter.

Também tem um botão de "Adicionar ao carinho" na página de detalhes do produto. Inclusive, este botão recebe um ícone que não está bem alinhado - podemos corrigir isso logo mais. Por fim, na página do carrinho, também há alguns botões para "Continuar comprando" e "Ir para pagamento".

No VS Code, na pasta "src > components > Button", temos um componente de botão no arquivo index.tsx, onde podemos aplicar o princípio da responsabilidade única, também conhecido como single responsability principal ou SRP.

Atualmente, o botão não adere a esse princípio, pois nas linhas 38 e 39, há duas tags <span>: um para receber e renderizar um ícone e outro para receber e renderizar um texto e um children.

O princípio diz que um botão deve ser bom em uma única tarefa, que é renderizar um texto. Portanto, vamos apagar essas tags e modificar o código para que ele lide apenas com o children. Assim, o que passarmos para o botão será renderizado.

Na linha 34, percebemos que o CSS do botão não possui uma classe chamada button, mas sim um seletor. Vamos limpar esse CSS. No className, vamos remover o Styles.button.

Também podemos limpar a propriedade onClick, que é passada como parâmetro para o botão, para depois ser passada ao handleClick. Não precisamos do handleClick; podemos passar a propriedade onClick diretamente para o evento onClick.

Como removemos o ícone e o texto, podemos pagar text e icon das linhas 19 e 20, onde ficam os argumentos recebidos pelo botão.

Button/index.tsx:

const Button = ({
  children,
  variant = "primary",
  size = "medium",
  onClick,
  style,
  ...props
}: ButtonProps) => {
  return (
    <button
      style={style}
      className={classnames(Styles[variant], Styles[size])}
      onClick={onClick}
      {...props}
    >
      {children}
    </button>
  );
};

Agora, no type ButtonProps, também podemos removemos o texto e o ícone.

type ButtonProps = {
  style?: CSSProperties;
  children?: ReactNode;
  variant?: "primary" | "secondary";
  size?: "small" | "medium" | "large"; // Define diferentes tamanhos
  onClick: (e: MouseEvent<HTMLElement>) => void; // Manipulação de click adicional
};

No entanto, se verificamos a página do carrinho no navegador, apenas o botão que renderiza a string "Ok" funciona. Os outros, como "Ir para o pagamento" e "Continuar comprando", não têm mais rótulo.

Na home, só o corpo do botão "Ver as novidades" foi renderizado, enquanto o botão da newsletter está correto. Na página do produto, o botão "Adicionar ao carrinho" renderiza o rótulo, mas não o ícone.

Voltando ao VS Code, precisamos refatorar quem usa o botão para passar as propriedades corretas, pois mudamos sua assinatura. No menu lateral esquerdo, vamos fazer uma busca por Button, clicando na opção "Search" (ou "Ctrl + Shift + F").

Começaremos modificando o arquivo index.tsx da "Homepage". No <HeroBanner>, encontramos uma tag <Button> que possui a propriedade text do botão. Vamos apagar o text e passar o rótulo "Ver as novidades" como children, ou seja, entre as tags de abertura e fechamento do <Button>.

HomePage/index.tsx:

<HeroBanner
    backgroundImage="https://raw.githubusercontent.com/gss-patricia/use-dev-assets/refs/heads/main/banner-seceos-tablet.png"
    mainImage="https://raw.githubusercontent.com/gss-patricia/use-dev-assets/8df6d50256e4b270eb794ccbc0314baf2a656211/hero.png"
>
    <Typography variant="h1">
        Hora de abraçar seu{" "}
        <span style={{ color: "#8fff24" }}>lado geek!</span>
    </Typography>
    <Button onClick={() => console.log("ver novidades")} size="large">
        Ver as novidades!
    </Button>
</HeroBanner>

A próxima página será a página do carrinho. Em CartPage/index.tsx, há três botões.

Na tag <Button> da linha 110, vamos remover a propriedade text e passar o rótulo "Continuar comprando" como children. Na linha 114, faremos o mesmo com o rótulo "Ir para pagamento".

Na linha 64, o rótulo do botão "Excluir" já está sendo passado como children, portanto, não precisamos alterá-lo.

CartPage/index.tsx:

<div className={Styles.cartActions}>
    <Button onClick={handleRedirect} variant="secondary">
        Continuar comprando
    </Button>
    <Button onClick={() => console.log("pagamento")}>
        Ir para pagamento
    </Button>
</div>

Na página de detalhes do produto, em ProductDetail.tsx, o rótulo do botão "Adicionar ao carrinho" na linha 74 já está sendo passada corretamente.

Porém, não passamos mais a propriedade icon. Para resolver isso, podemos copiar o ícone <AddCarrinhoIcon />, apagar a propriedade icon e passá-lo como filho. Dessa forma, botão encapsulará tanto o rótulo quanto o ícone.

ProductDetail.tsx:

<div className={Styles.action}>
    <Button onClick={handleAddToCart}>
        <AddCarrinhoIcon />
        Adicionar ao carrinho
    </Button>
</div>

Na página de detalhes do produto no navegador, verificamos que o ícone já é renderizado junto com o rótulo no botão "Adicionar ao carrinho", mas não há espaçamento entre o rótulo e o ícone.

Para corrigir esse problema, acessamos o arquivo Button.module.css. No seletor button, abaixo da linha 12, vamos adicionar a propriedade gap com 8px.

Button.module.css:

button {
  font-weight: bold;
  cursor: pointer;
  transition: background-color 0.3s ease;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 32px;
  border: 1px solid var(--roxo);
  padding: 16px 24px;
  font-size: 20px;
  line-height: 24px;
  gap: 8px;
}

Finalmente, podemos verificar se todos os botões estão sendo renderizados corretamente na página inicial, na página de detalhes do produto e no carrinho.

Conclusão

O princípio da responsabilidade única diz que um componente ou módulo deve fazer apenas o que foi destinado a fazer.

O botão estava se preocupando com tarefas fora de seu escopo, como renderizar ícones. Por exemplo, em uma casa, cada cômodo é desenhado para uma tarefa específica. Podemos dormir no quarto, cozinhar na cozinha, mas não cozinhar no banheiro.

Neste contexto, o botão deve apenas receber um filho. Se futuramente o ícone precisar estar em outra posição ou o espaçamento precisar ser alterado, seria confuso mudar o comportamento do botão. Por isso, dividimos o código em pequenas partes que fazem sua responsabilidade muito bem.

Cada princípio do SOLID é importante e se complementa. O Single Responsibility abre portas para que possamos implementar os outros princípios, mostrando como eles se conectam.

Princípio da responsabilidade única - Refatoração do Input

Da mesma forma que refatoramos o botão, removendo suas responsabilidades de renderizar o ícone, também refatoraremos o input.

Refatorando input

Com o VS Code aberto, vamos acessar o código do input no arquivo index.tsx, dentro da pasta "src > components > Input". Observamos que o input está realizando muitas funções.

Ele está renderizando o ícone, quando deveria ser apenas um campo de entrada. Portanto, podemos remover o icon retornado na linha 35 e a propriedade icon na linha 16, pois não serão necessários. Em InputProps, também podemos apagar a tipagem do icon na linha 5.

Na linha 1, importamos o ReactNode, que não será mais utilizado, então podemos limpar o código.

Input/index.tsx:

import { CSSProperties } from "react";
import Styles from "./Input.module.css";

type InputProps = {
  variant?: "primary" | "secondary";
  placeholder?: string;
  value?: string;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  id?: string;
  style?: CSSProperties;
  type?: string;
};

const Input = ({
  variant = "primary",
  onChange,
  placeholder,
  id,
  style,
  type = "text",
  ...props
}: InputProps) => {
  return (
    <div className={`${Styles.inputContainer} ${Styles[variant]}`}>
      <input
        type={type}
        style={style}
        {...props}
        onChange={() => onChange}
        id={id}
        placeholder={placeholder}
      />
    </div>
  );
};

export default Input;

Já fizemos a limpeza, mas há uma melhoria que podemos implementar. O input é um componente compartilhado, ou seja, é usado em várias páginas por diversos componentes.

E na linha 24, temos uma <div> com a classe inputContainer. No CSS do input, essa classe faz um posicionamento relative. No entanto, isso não é uma boa prática, pois quem utiliza o input deve controlar seu posicionamento: se é relativo, absoluto ou sem posicionamento.

Por isso, não usaremos essa div e podemos removê-la. Mas, ainda queremos a variante do input, que possui estilos primário e secundário. Então, copiamos o Styles[variant] presente na div e passamos um className igual ao Styles[variant]. Dessa forma, mantemos a variante.

const Input = ({
  variant = "primary",
  onChange,
  placeholder,
  id,
  style,
  type = "text",
  ...props
}: InputProps) => {
  return (
    <input
      type={type}
      className={Styles[variant]}
      style={style}
      {...props}
      onChange={() => onChange}
      id={id}
      placeholder={placeholder}
    />
  );
};

Vamos conferir o resultado da aplicação no navegador. No componente header, o input já está diferente do estilo original proposto no Figma. O estilo original tem bordas e preenchimento em cinza-claro. Precisamos corrigir no CSS, já que movemos a variante que estava no contêiner da <div> e a colocamos diretamente no <input>.

Na linha 18 do arquivo Input.module.css, vamos removendo a seleção direta dos elementos input dentro das classes .primary e .secondary. Agora, os estilos aplicam-se ao próprio elemento que possui essas classes. Fazemos isso tanto para o estilo padrão, quanto para o hover e o focus de .primary e .secondary.

Além disso, não precisaremos mais do .inputContainer e nem do .iconContainer, pois não faz parte do escopo do input. O estilo do ícone deve ser aplicado no próprio ícone.

Falta apenas adicionar o background do campo de input que deve ser um cinza-claro. Inclusive, já temos essas variáveis de cores prontas na aplicação. No estilo padrão de .secondary, além da borda em --cinza-claro, vamos acrescentar a propriedade background-color com a mesma cor da borda.

Input.module.css:

.primary:hover,
.secondary:hover {
  border-color: var(--rosa);
}

.primary:focus,
.secondary:focus {
  border-color: var(--rosa);
  outline: none;
}

.primary {
  background-color: transparent;
}

.secondary {
  border-color: var(--cinza-claro);
  background-color: var(--cinza-claro);
}

Agora, o estilo do input está correto, mas falta o ícone de lupa alinhado à direita do campo.

Sabemos que o input está sendo usado no componente header. Por isso, vamos acessar o diretório "src > components > Header" e abrir o index.tsx. Na linha 51, o icon está em vermelho, indicando que estamos passando a propriedade ícone, que não é mais necessária. Por isso, podemos apagá-la.

Precisamos usar o <SearchIcon> no contêiner do <Input> de pesquisa. Na linha 53, há um botão nativo do HTML que não deveria ser usado. Vamos usar o botão correto, que é o <Button>.

Inclusive, já aplicamos o princípio da responsabilidade única no botão, portanto, ele trabalha com o recebimento de children. Isso significa que podemos passar um ícone através desse componente, ou seja, o <SearchIcon>. Também importamos o componente Button, que ainda não havia sido importado.

Na tag de abertura do <Button>, vamos passar o onClick, pois precisamos dele.

No searchContainer, temos o Input e o Button. Como o botão aceita children, passando o ícone. Assim, o botão com o ícone é renderizado no navegador. Porém, o posicionamento está incorreto.

Para solucionar esse problema, vamos inspecionar esse botão com o DevTools com o atalho "Ctrl + Shift + C". No botão que contém o ícone de lupa, testamos o posicionamento relativo e uma margem direita de 36 pixels.

Depois de verificar que essa posição funcionou, podemos copiar o estilo CSS. Assim, já sabemos qual estilo passar para o botão no VS Code. Também sabemos que o botão aceita a propriedade de estilo, pois tinha a tipagem style que aceita propriedades CSS. Nesse style, passamos position como relative e right como 36px.

Header/Index.tsx:

import Button from "../Button";

<div className={Styles.searchContainer}>
    <Input
        variant="secondary"
        value={query}
        onChange={handleInputChange}
        placeholder="O que você procura?"
    />
    <Button
        style={{ position: "relative", right: "36px" }}
        onClick={handleSearch}
    >
        <SearchIcon />
    </Button>
</div>

No navegador, o ícone de lupa está renderizado corretamente. Inclusive, o botão é acessível e focável.

Também podemos passar por todas as páginas e conferir os campos de input, como o campo para digitar o endereço de e-mail na seção de newsletter e o campo para digitar o cupom na página do carrinho.

O input é um componente para receber entrada, sem responsabilidades adicionais. Com essa composição, quem o utiliza que deve se preocupar com seu posicionamento ou ícone.

Sobre o curso SOLID aplicado ao React: melhorando a modularidade e flexibilidade do código

O curso SOLID aplicado ao React: melhorando a modularidade e flexibilidade do código possui 161 minutos de vídeos, em um total de 46 atividades. Gostou? Conheça nossos outros cursos de React em Front-end, ou leia nossos artigos de Front-end.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda React acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas