Fullstack com Node.js, React e GraphQL – 8: Frontend React e TypeScript

Fala, galera! Depois de uma longa pausa estamos de volta, e nesta oitava parte da série Fullstack com Node.js, React e GraphQL vamos começar a desenvolver o frontend com React, usando a linguagem TypeScript, escrevendo CSS com styled-components e a biblioteca de componentes Material-UI.

Mas antes…

Como o projeto ficou um tempo parado, resolvi atualizar todas as dependências para deixar o projeto atual. Algumas atualizações tinham breaking changes, mas atualizei o que foi necessário e está tudo funcionando no repositório.

Uma das principais mudanças foi que o Apollo Server agora oferece o GraphQL Playground, ao invés do GraphiQL, como interface gráfica para testar queries. Mas o funcionamento é praticamente o mesmo.

Escolha da stack frontend

Como este é o primeiro post que envolve frontend, vou dar uma breve explicação da escolha das tecnologias:

TypeScript

O uso do TypeScript vem crescendo nos últimos anos (veja abaixo o gráfico de downloads nos últimos 2 anos), e é cada vez mais importante que o desenvolvedor web tenha esse conhecimento no seu arsenal.

Downloads do módulo typescript no NPM (via npmtrends.com)

O TypeScript adiciona um pouco mais de complexidade ao setup e deixa o código mais verboso, com a adição das declarações de tipos. Porém isso é compensado pelas vantagens, como detectar bugs ainda durante a codificação, melhorar o auto-complete do editor, facilitar refatorações de código.

Styled Components

styled-components é uma lib para escrita de CSS que vem em uma crescente, assim como o TypeScript. Ela traz o paradigma CSS-in-JS, que encoraja o acoplamento do CSS com o HTML e JS de um componente.

Mesmo que isso não pareça uma boa ideia a princípio, ela traz vantagens como:

  • Carrega apenas o CSS necessário para os componentes que estão na tela.
  • Elimina conflito de estilos entre componentes.
  • Facilita a manutenção: todo CSS que afeta um componente está junto dele.
  • Facilita criação de estilos dinâmicos, baseados na propriedades e estado do componente.

Material-UI

Vamos usar o padrão Material Design, do Google, para facilitar a implementação da parte visual da aplicação.

Dentre as implementações do Material Design em componentes React, o Material-UI é uma das mais maduras e completas.

Fazendo o setup

Babel

Para fazer a transpilação do código TypeScript para JavaScript (que é o que o navegador entende) vamos usar o Babel.

Para isso vamos instalar nas dependências de desenvolvimento:

npm i -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-proposal-object-rest-spread

Além do core do Babel, estamos instalando:

  • @babel/preset-env: transforma features mais novas do JavaScript para código compatível com navegadores que não suportam nativamente
  • @babel/preset-react: transforma código JSX do React em JavaScript.
  • @babel/preset-typescript: transforma TypeScript em JavaScript.
  • @babel/plugin-proposal-object-rest-spread: adiciona suporte a object rest spread, que ainda não está na versão oficial do JavaScript (mas está a caminho)

Configuração

Nossa configuração do Babel, que fica no arquivo .babelrc, é bem simples. Apenas ativamos os presets e o plugin que instalamos:

{
  "presets": ["@babel/env", "@babel/typescript", "@babel/react"],
  "plugins": [
    "@babel/proposal-object-rest-spread"
  ]
}

Webpack

Para usarmos o webpack, vamos instalar as dependências de desenvolvimento:

npm i -D babel-loader webpack webpack-cli webpack-dev-server

🤔 Não sabe pra que serve o webpack ou como ele funciona? Leia a série de posts Webpack sem Medo, onde explico tudo.

O que estamos instalando?

  • babel-loader: loader que integra com o Babel para fazer a transpilação do código.
  • webpack: o core do webpack
  • webpack-cli: cli (interface de linha de comando) do webpack, necessária para executar alguns comandos, como fazer o build dos assets.
  • webpack-dev-server: servidor de desenvolvimento integrado com o webpack, para processar os arquivos a medida que vamos desenvolvendo.

Configuração

A configuração do webpack, no arquivo webpack.config.js, fica assim:

const path = require('path');

module.exports = env => ({
  mode: env === 'prod' ? 'production' : 'development',

  entry: './src/index',

  output: {
    path: path.resolve('dist'),
    filename: 'bundle.js'
  },

  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json']
  },

  module: {
    rules: [
      {
        test: /\.(tsx?)|(js)$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          cacheDirectory: true
        }
      }
    ]
  }
});

Alguns pontos a se notar:

mode: env === 'prod' ? 'production' : 'development'
Usamos o parâmetro de linha de comando env para setar o modo produção ou desenvolvimento.

extensions: ['.ts', '.tsx', '.js', '.json']
Adicionamos as extensões .ts e .tsx para trabalhamos com TypeScript.

A única regra que temos em modules trata os arquivos com extensões .ts, .tsx e .js, passando para o babel-loader, que vai cuidar das transformações.

NPM scripts

Para facilitar a nossa vida, vamos adicionar dois scripts no package.json, o start para iniciar o servidor de desenvolvimento e o build para compilar em modo de produção:

  "scripts": {
    "start": "webpack-dev-server",
    "build": "webpack --env prod"
  }

TypeScript

Vamos instalar como dependência de desenvolvimento:

npm i -D typescript

Esta instalação disponibiliza o executável tsc (TypeScript compiler).

Configuração

O TypeScript usa o arquivo de configuração tsconfig.json. Podemos gerar uma versão inicial deste arquivo rodando:

npx tsc --init

Ele vai gerar um tsconfig.json com valores default (e mais um monte de comentários explicando resumidamente cada opção).

Nosso tsconfig, após algumas alterações, ficou assim:

{
  "compilerOptions": {
    "target": "esnext", 
    "module": "commonjs", 
    "jsx": "preserve", 
    "strict": true, 
    "esModuleInterop": true 
  }
}

Fizemos duas alterações na config padrão:

"target": "esnext"
Deixamos o compilador de TypeScript compilar para a versão mais avançada de JavaScript. Daí pra frente, para compilar funcionalidades específicas para versões mais compatíveis dos navegadores fica a cargo do Babel.

"jsx": "preserve"
Temos que habilitar o parsing de JSX, mas sem fazer nenhuma alteração. Transformar JSX em JS fica a cargo do Babel com o preset-react.

React, Styled Components, Material UI

Agora que preparamos o palco, vamos trazer o atores principais:

npm i react react-dom styled-components @material-ui/core

É recomendável instalar também as definições de tipos para essas libs:

npm i @types/react @types/react-dom @types/styled-components

🤔 Porque eu preciso destas instalações extra?

Muitas libs não possuem suas próprias definições de tipos. Quando importamos um módulo destes em um arquivo TypeScript, o compilador assume o tipo any para o que está sendo importado.

O tipo any significa que pode ser qualquer coisa, ou basicamente que nenhum tipo foi definido. Como estamos em modo strict (definido no tsconfig), o compilador requer que os módulos tenham tipos definidos.

Nem todo autor de libs tem interesse em adicionar definições de tipos em suas libs. Para estes casos existe o DefinitelyTyped, uma iniciativa open-source onde pessoas contribuem com definições de tipos para várias libs. Estas definições da comunidade são publicadas no NPM sob o escopo @types.

Por isso instalamos os @types/… ali em cima. Além de cumprir as exigências do compilador, é mais fácil trabalhar com libs que possuem tipos definidos. As sugestões de auto-complete e as indicações de erro no editor ficam muito mais inteligentes.

Vamos criar um index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta
    name="viewport"
    content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
  >
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>React</title>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
</html>

Veja que adicionamos uma referência para a fonte Roboto, do Google Fonts, que é a fonte utilizada pelo Material UI.

Vamos criar também o ponto de entrada para nossa aplicação, o src/index.tsx:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App name="Web Dev Drops" />, document.getElementById('app'));

O primeiro componente

Vamos criar o componente src/App.tsx que é referenciado no index. Por enquanto este componente vai servir apenas para testar o nosso setup.

Veja o componente inteiro. Na sequência eu explico cada parte.

import React, { useState } from 'react';
import styled from 'styled-components';
import Button from '@material-ui/core/Button';
import CssBaseline from '@material-ui/core/CssBaseline';
import { StylesProvider } from '@material-ui/core/styles';

const StyledH1 = styled.h1`
  font-size: 30px;
  text-transform: uppercase;
`;

type Props = {
  name: string;
};

const App: React.FC<Props> = ({ name }) => {
  const [who, setWho] = useState('World');

  return (
    <StylesProvider injectFirst>
      <CssBaseline />
      <StyledH1>React App</StyledH1>
      <p>
        Hello {who}! This is {name}!
      </p>
      <Button variant="contained" color="primary">
        Material Button
      </Button>
    </StylesProvider>
  );
};

export default App;

Styled component

A primeira coisa que ele faz é declarar um componente StyledH1 usando o styled-components.

const StyledH1 = styled.h1`
  font-size: 30px;
  text-transform: uppercase;
`;

O que ele faz aqui é criar um componente que renderiza uma tag h1 com aqueles estilos aplicados.

A magia aqui é que eu posso usar o StyledH1 várias vezes e ele sempre vai vir com os estilos. Não preciso me preocupar em criar um arquivo .css e importá-lo no componente. Estilos de outros componentes não vão conflitar com este.

Declarando props com tipos

O próximo passo é declarar as props que o componente recebe, especificando os tipos.

type Props = {
  name: string;
};

O componente App só recebe uma prop name, do tipo string. Com essa declaração o TypeScript vai validar se eu tentar passar alguma prop diferente ou com o tipo errado, dando erro na compilação.

O componente

Por fim, vamos declarar o componente:

const App: React.FC<Props> = ({ name }) => {
  const [who, setWho] = useState('World');

  ...
}

Aqui eu declaro um componente funcional (React.FC) que recebe as pros declaradas mais acima (React.FC<Props>).

Na segunda linha eu declaro uma variável de estado do componente, usando o hook useState.

Na sequência eu retorno o que o componente vai renderizar:

return (
    <StylesProvider injectFirst>
      <CssBaseline />
      <StyledH1>React App</StyledH1>
      <p>
        Hello {who}! This is {name}!
      </p>
      <Button variant="contained" color="primary">
        Material Button
      </Button>
    </StylesProvider>
  );

A primeira tag, <StylesProvider injectFirst>, serve para controlar como o Material UI vai injetar seus estilos. Com injectFirst você consegue sobrescrever com seus próprios estilos se quiser.

<CssBaseline /> também é do Material UI e adiciona um reset de estilos.

Depois a gente usa o nosso componente StyledH1 com um texto, exibimos a variável de estado who e a prop name, e no fim adicionamos um botão do Material UI pra conferir se ele é renderizado normalmente.

O resultado é este:

Nada muito empolgante, mas conseguimos validar o nosso setup. Na próxima parte vamos começar a criar as telas da nossa aplicação de fato.

Resultado final

O código do projeto até este ponto está em: https://github.com/doug2k1/my-money/tree/v7.0.0

No próximo capítulo

Na próxima parte vamos criar as telas da aplicação, usando o setup de frontend que fizemos hoje.

Stay tuned!

Feedbacks?

E aí, o que está achando até agora? Algo que precisa melhorar?

Tags: | |

Sobre o Autor

Douglas Matoso
Douglas Matoso

Desenvolvedor web desde 2008. Criador do Web Dev Drops.

2 Comentários

  1. Estou deveras feliz em ver que ainda está dando continuidade a esse material. Está excelente o conteúdo, parabéns e continue assim!


Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *