Neste artigo vou mostrar um dos projetos que construí na formação React da Rocketseat, um projeto de duas páginas/telas, onde uma tela contém o timer, e a outra tela contém o histórico dos ciclos realizados.
Já que uma das melhores maneiras de se aprender é ensinando, resolvi criar esta postagem para revisitar e fixar melhor os fundamentos da biblioteca React.
Caso queira adquirir os cursos da Rocketseat com o meu cupom de desconto Acesse esse link
Ao longo do projeto vamos ver sobre vários assuntos:
- Estilização com
Styled Components
; - Roteamento de páginas com
React Router
; - Distribuição de estado com
Context API
; - Efeito colateral do hook
useEffect
; - entre outros assuntos...
Nesta primeira parte iremos cobrir a estrutura da aplicação, a criação do projeto e a utilização da biblioteca Styled Components
para estilização da nossa aplicação.
Links úteis:
Capítulos:
1 - Criação do projeto
1.1 - Usando o Vite
Vamos criar o projeto usando uma ferramenta chamada Vite que consegue fazer toda a configuração de um projeto frontend, configurando, por exemplo, o bundler e o compiler da nossa aplicação, nos poupando bastante tempo.
$ npm create vite@latest
Após executar o comando, dê um nome para a aplicação, após selecione React e TypeScript.
commit: feat: 🎉 add initial structure $ npm create vite@latest
1.2 - Instalando as dependências
Após o projeto ter sido criado, na raiz do projeto, instale as dependências com o comando:
$ npm i
commit: build: 🔧 install dependencies $ npm i
1.3 - (Opcional) Removendo arquivos desnecessários
commit: refactor: 🔥 remove vite unused files
2 - Styled Components
2.1 - Instalando o pacote styled-components
Execute o comando na raiz do projeto:
$ npm i styled-components
commit: build: ➕ add styled-components $ npm i styled-components
2.2 - Instalação da tipagem
O pacote styled-components
era um daqueles pacotes que não traziam a tipagem do TypeScript junto dele:
desta forma, era preciso instalar a tipagem separadamente com esse comando:
# NÃO É MAIS PRECISO INSTALAR $ npm i @types/styled-components -D
-D
é uma abreviatura de--save-dev
que significa que será instalado apenas para desenvolvimento, ou seja, essa biblioteca não fará parte dobuild
final da aplicação em produção. Isso porque o código TypeScript é convertido todo para JavaScript, então os tipos não serão usados no código final em produção.
Hoje em dia nas versões mais atuais, isso não é mais preciso, pois a tipagem já vem junto dele:
Você pode ignorar os commits abaixo, pois um deles possue a instalação dos tipos e o outro a desinstalação. Isso ocorreu, pois as aulas do Rocketseat estavam desatualizadas no momento. Mas como eu instalei a versão mais recente, pude desinstalar sem problema o pacote dos tipos.
commit: build: ➕ add types of styled-components $ npm i @types/styled-components -D
commit: build: ➖ remove @types/styled-components
with $ npm uninstall @types/styled-components
2.3 - Instale a extensão no VSCode
Essa extensão habilita o Syntax highlighting
, ou seja, deixa o código CSS colorido dentro do Tagged templates
2.4 - Entendendo como funciona o styled-components
Vamos criar um componente de botão para exemplificar o funcionamento do styled-components
.
Crie o arquivo src/components/Button.tsx
com o código:
export function Button() { return <button>Enviar</button> }
Em src/App.tsx
vamos repetir várias vezes o botão em tela:
import { Button } from "./components/Button"; export function App() { return ( <> <Button /> <Button /> <Button /> <Button /> </> ) }
Os sinais
<>
e</>
chamadoFragment
é usado quando precisamos colocar vários components dentro sem precisar de uma tagdiv
Vamos agora adicionar uma propriedade para mudar a cor do botão.
Em src/components/Button.tsx
adicionamos uma tipagem das cores aceitas para cada botão:
interface ButtonProps { variant?: 'primary' | 'secondary' | 'danger' | 'success'; } export function Button(props: ButtonProps) { return <button>Enviar</button> }
Em src/App.tsx
, para cada botão, vamos adicionar a propriedade de cor variant
:
import { Button } from "./components/Button"; export function App() { return ( <> <Button variant="primary" /> <Button variant="secondary" /> <Button variant="success" /> <Button variant="danger" /> <Button /> </> ) }
Quando estamos utilizando o CSS tradicional, para estilizar precisamos utilizar as classes. Para isso, vamos utilizar o css-modules apenas para este exemplo, pois, mais adiante, vamos utilizar apenas o styled-components
.
Criando o arquivo de css-modules
em src/components/Button.module.css
com as classes:
.button { width: 100px; height: 40px; }
vamos importar os estilos e colocar o className
em src/components/Button.tsx
import styles from "./Button.module.css"; interface ButtonProps { variant?: 'primary' | 'secondary' | 'danger' | 'success'; } export function Button(props: ButtonProps) { return <button className={styles.button}>Enviar</button> }
e o resultado agora são botões com tamanhos maiores:
e com as cores no mesmo arquivo src/components/Button.tsx
:
import styles from "./Button.module.css"; interface ButtonProps { variant?: 'primary' | 'secondary' | 'danger' | 'success'; } export function Button({ variant = 'primary' }: ButtonProps) { return <button className={`${styles.button} ${styles[color]}`}>Enviar</button> }
Agora falta criar as classes para as cores. No arquivo de CSS src/components/Button.module.css
:
.button { width: 100px; height: 40px; } .primary { background-color: purple; } .secondary { background-color: orange; } .danger { background-color: red; } .success { background-color: green; }
e o resultado das cores é esse:
Refatorando usando Styled Components
Vamos renomear o arquivo de CSS src/components/Button.module.css
para ➡️ src/components/Button.styles.ts
terminando exatamente com .ts
e o conteúdo:
import styled from "styled-components"; export const ButtonContainer = styled.button` width: 100px; height: 40px; `;
e voltando no arquivo src/components/Button.tsx
vamos trocar a tag button
para ButtonContainer
:
import { ButtonContainer } from "./Button.styles"; interface ButtonProps { variant?: 'primary' | 'secondary' | 'danger' | 'success'; } export function Button({ variant = "primary" }: ButtonProps) { return <ButtonContainer>Enviar</ButtonContainer>; }
Como resultado temos os botões com os mesmos tamanhos, pois definimos o height
e o width
no ButtonContainer
:
Podemos definir a cor adicionando variant={variant}
no mesmo arquivo src/components/Button.tsx
:
import { ButtonContainer } from "./Button.styles"; interface ButtonProps { variant?: 'primary' | 'secondary' | 'danger' | 'success'; } export function Button({ variant = "primary" }: ButtonProps) { return <ButtonContainer variant={variant}>Enviar</ButtonContainer>; }
e no arquivo de estilização src/components/Button.styles.ts
:
import styled from "styled-components"; interface ButtonContainerProps { variant: "primary" | "secondary" | "danger" | "success"; } export const ButtonContainer = styled.button` width: 100px; height: 40px; `;
nesse mesmo arquivo vamos criar um tipo para variant:
import styled from "styled-components"; export type ButtonVariant = "primary" | "secondary" | "danger" | "success"; interface ButtonContainerProps { variant: ButtonVariant; } export const ButtonContainer = styled.button` width: 100px; height: 40px; `;
E no arquivo src/components/Button.tsx
vamos importar e usar essa tipagem:
import { ButtonContainer, ButtonVariant } from "./Button.styles"; interface ButtonProps { variant?: ButtonVariant; } export function Button({ variant = "primary" }: ButtonProps) { return <ButtonContainer variant={variant}>Enviar</ButtonContainer>; }
Para completar a tipagem e fazer uso dela, vamos adicionar <ButtonContainerProps>
import styled from "styled-components"; export type ButtonVariant = "primary" | "secondary" | "danger" | "success"; interface ButtonContainerProps { variant: ButtonVariant; } export const ButtonContainer = styled.button<ButtonContainerProps>` width: 100px; height: 40px; `;
Tipagem feita, agora precisamos setar as cores no mesmo arquivo de estilização src/components/Button.styles.ts
adicionando a constante buttonVariants
:
import styled from "styled-components"; export type ButtonVariant = "primary" | "secondary" | "danger" | "success"; interface ButtonContainerProps { variant: ButtonVariant; } const buttonVariants = { primary: "purple", secondary: "orange", danger: "red", success: "green", }; export const ButtonContainer = styled.button<ButtonContainerProps>` width: 100px; height: 40px; `;
e fazendo uma interpolação de strings ${}
, o Styled Components
envia todas as propriedades automaticamente para a função:
import styled from "styled-components"; export type ButtonVariant = "primary" | "secondary" | "danger" | "success"; interface ButtonContainerProps { variant: ButtonVariant; } const buttonVariants = { primary: "purple", secondary: "orange", danger: "red", success: "green", }; export const ButtonContainer = styled.button<ButtonContainerProps>` width: 100px; height: 40px; ${props => { return `background-color: ${buttonVariants[props.variant]}` }} `;
o props.variant
é o valor que vem automaticamente do arquivo do componente do botão.
E o resultado com as cores permanece identicando:
Conclusão do Styled Components: A vantagem é não precisar usar multiplas classes para a estilização e mantendo um scopo de componente.
Apenas uma melhoria na questão de Syntax highlighting
:
Para melhorar podemos importar o css e acrescentar a palavra css na frente da template literals:
commit: feat: ✨ add button component to understand about styled-components
2.5 - Configurando Temas
Crie o arquivo src/styles/themes/default.ts
com o seguinte código:
export const defaultTheme = { white: "#FFF", primary: "#8257e6", secondary: "orange", };
No arquivo src/App.tsx
vamos importar o defaultTheme
e vamos passar como valor a propriedade theme
do ThemeProvider
:
import { ThemeProvider } from "styled-components"; import { Button } from "./components/Button"; import { defaultTheme } from "./styles/themes/default"; export function App() { return ( <ThemeProvider theme={defaultTheme}> <Button variant="primary" /> <Button variant="secondary" /> <Button variant="success" /> <Button variant="danger" /> <Button /> </ThemeProvider> ); }
Se tivessemos mais de um tema, poderíamos trocar o valor
defaultTheme
paralightTheme
por exemplo:theme={lightTheme}
Podemos acessar as cores do tema usando props.theme.nomeDaPropriedade
:
` background-color: ${(props) => props.theme.primary}; `
No arquivo do botão src/components/Button.styles.ts
:
... export const ButtonContainer = styled.button<ButtonContainerProps>` width: 100px; height: 40px; border-radius: 4px; border: 0; margin: 8px; background-color: ${(props) => props.theme.primary}; color: ${(props) => props.theme.white}; `;
e temos esse resultado:
commit: feat: ✨ add themes
2.6 - Tipagem de Temas
Temos um problema de autocomplete ao digitar props.theme.
o VSCode não nos fornece as opções disponíveis, isso acontece por falta de configuração de tipagem:
Para corrigir isso, vamos criar um arquivo no diretório src/@types/styled.d.ts
.
A nomenclatura @types
foi usada nesse caso simplismente para permanecer no topo da árvore de pastas. Além de ficar com o ícone do TypeScript.
Já a nomenclatura do arquivo styled.d.ts
terminando com d.ts
, indica que esse arquivo possuí apenas código de tipagem do TypeScript, chamado de arquivo de definição de tipos.
Nesse arquivo src/@types/styled.d.ts
vamos adicionar duas importações:
import "styled-components"; import { defaultTheme } from "../styles/themes/default";
A imagem acima mostra o ponteiro do mouse descansando em cima da const defaultTheme
, indicando que a devolução do valor é do tipo objeto, com as propriedades do tipo string, ou seja, mostra a tipagem do defaultTheme
. Isso acontece pois o TypeScript cria a tipagem de forma automática pra gente, mas existe uma forma de nós acessarmos isso e guardar em uma variável com o seguinte código:
type ThemeType = typeof defaultTheme; // ThemeType poderia ter qualquer nome
Na imagem acima podemos perceber que o tipo gerado (inferência de tipo) para o defaultTheme
foi guardado dentro de ThemeType
, isso graças ao typeof
que capturou a tipagem do defaultTheme
.
No arquivo src/@types/styled.d.ts
vamos fazer o uso do declare module
:
import "styled-components"; import { defaultTheme } from "../styles/themes/default"; type ThemeType = typeof defaultTheme; declare module "styled-components" { // Aqui vamos subscrever a tipagem do DefaultTheme }
O declare module
cria uma tipagem para o módulo styled-components
do NPM, e toda vez que o styled-components
for importado, a tipagem, a definição de tipos que vai ser puxada, vai ser o que foi definido dentro do scopo do declare module
.
Como queremos apenas subscrever algo de dentro do declare module
, e não criar toda uma tipagem nova, nós tivemos que fazer a importação import "styled-components";
e fazer a declaração com o declare module "styled-components"
. Sem a importação estaríamos criando tudo do zero a definição de tipos do pacote styled-components
.
Na imagem acima, mostra o arquivo src/components/Button.styles.ts
, e nele o ponteiro do mouse está parado em cima da palavra theme
. Se clicar em cima de theme
segurando a tecla ctrl
ou command
, é direcionado para o arquivo de tipos do styled-components
:
Agora clicando em cima de DefaultTheme
em laranja, é direcionado para a exportação da tipagem do DefaultTheme
:
Na imagem acima, podemos ver que o objeto {}
do DefaultTheme
exportado está vazio, pois esse objeto foi feito para ser subscrito pelas nossas tipagens.
Então o código final do arquivo src/@types/styled.d.ts
ficará assim:
import "styled-components"; import { defaultTheme } from "../styles/themes/default"; type ThemeType = typeof defaultTheme; // subescreve a tipagem do DefaultTheme declare module "styled-components" { export interface DefaultTheme extends ThemeType {} }
Agora no arquivo src/components/Button.styles.ts
podemos ver a tipagem funcionando:
e se tentar colocar uma propriedade que não existe, um erro será acusado:
commit: feat: ✨ add type to themes and override DefaultTheme
2.7 - Estilos Globais
Criando o arquivo no diretório src/styles/global.ts
. Mais uma vez terminando com .ts
pois estamos utilizando styled-components
. Todo CSS global vamos colocar nesse arquivo:
import { createGlobalStyle } from "styled-components"; export const GlobalStyle = createGlobalStyle` * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #333; color: #FFF; } `;
Vamos agora importar no arquivo src/App.tsx
e utilizar:
import { ThemeProvider } from "styled-components"; import { Button } from "./components/Button"; import { GlobalStyle } from "./styles/global"; import { defaultTheme } from "./styles/themes/default"; export function App() { return ( <ThemeProvider theme={defaultTheme}> <Button variant="primary" /> <Button variant="secondary" /> <Button variant="success" /> <Button variant="danger" /> <Button /> {/* tanto faz a posição do <GlobalStyle /> dentro do ThemeProvider */} <GlobalStyle /> </ThemeProvider> ); }
Tanto faz a posição do <GlobalStyle />
dentro do ThemeProvider
, o importante é que <GlobalStyle />
esteja dentro do scopo do ThemeProvider
, senão o <GlobalStyle />
não terá acesso as variáveis do nosso tema.
Acessando a aplicação, podemos ver os estilos globais sendo aplicados, como o background na cor cinza:
commit: feat: ✨ add global styles
2.8 Cores e Fonte
Para este projeto foi escolhido a fonte Roboto e a fonte Roboto Mono
A fonte Roboto Mono
é uma fonte tipográfica monoespaçada, cujas letras e caracteres ocupam o mesmo espaço horizontal. Ela será ideal para ser usada no timer.
Adicionamos as fontes no arquivo index.html
:
<!doctype html> <html lang="pt"> <head> <meta charset="UTF-8" /> <!-- adicione essas duas linhas --> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <!-- o ideal é deixar os dois links acima por primeiro para carregar mais rápido as fontes --> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!-- e adicione também esta linha --> <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,700;1,700&family=Roboto:wght@400;700&display=swap" rel="stylesheet" /> <title>Ignite Timer</title> </head>
No arquivo de estilos globais src/styles/global.ts
:
import { createGlobalStyle } from "styled-components"; export const GlobalStyle = createGlobalStyle` * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #333; color: #FFF; } /* adicione */ body, input, textarea, button { font-family: 'Roboto', sans-serif; font-weight: 400; font-size: 1rem; } `;
Fonte tipográfica definida, agora vamos definir as cores no nosso tema, no arquivo src/styles/themes/default.ts
:
export const defaultTheme = { white: "#FFF", "gray-100": "#E1E1E6", "gray-300": "#C4C4CC", "gray-400": "#8D8D99", "gray-500": "#7C7C8A", "gray-600": "#323238", "gray-700": "#29292E", "gray-800": "#202024", "gray-900": "#121214", "green-300": "#00B37E", "green-500": "#00875F", "green-700": "#015F43", "red-500": "#AB222E", "red-700": "#7A1921", "yellow-500": "#FBA94C", };
Vamos utilizar as cores no arquivo src/styles/global.ts
:
import { createGlobalStyle } from "styled-components"; export const GlobalStyle = createGlobalStyle` * { margin: 0; padding: 0; box-sizing: border-box; } :focus { outline: none; box-shadow: 0 0 0 2px ${(props) => props.theme["green-500"]}; } body { background: ${(props) => props.theme["gray-900"]}; color: ${(props) => props.theme["gray-300"]}; } body, input, textarea, button { font-family: 'Roboto', sans-serif; font-weight: 400; font-size: 1rem; } `;
Ao digitar props.theme.
, por causa da tipagem, temos todas as cores disponiveis no autocomplete:
Pra finalizar essa parte de cores, precisamos arrumar o erro no arquivo de estilos do botão src/components/Button.styles.ts
:
Trocamos de props.theme.primary
para props.theme["green-500"]
.
Feito isso já podemos fisualizar o novo estilo:
commit: feat: 💄 add fonts and colors
Top comments (0)