React com Ionic e TypeScript

O Ionic React chegou oficialmente em outubro de 2019 e trouxe o suporte ao React para os componentes do Ionic, que até então eram usados principalmente com Angular. Neste artigo, você vai entender o que muda com essa novidade e acompanhar a criação de um app que lista os cursos do balta.io usando Ionic, React e TypeScript.

Eu sou um grande fã do Ionic, porém sempre fui do time Angular. Mesmo assim, a chegada do React ao ecossistema me chamou a atenção, e por isso resolvi testar tudo na prática.

O que é o Ionic React

O Ionic React é a implementação nativa do Ionic Framework para React. Em outras palavras, você escreve componentes React comuns e usa os mesmos elementos visuais que já fazem sucesso no Ionic com Angular.

Por baixo dos panos, o Ionic React funciona como uma camada sobre os web components do Ionic Core. Cada web component ganha um componente React correspondente, exportado de forma individual pelo pacote @ionic/react.

Além disso, o projeto usa a biblioteca react-dom padrão, e não uma camada nativa própria. Na prática, isso significa que todo o conhecimento que você já tem de HTML, CSS e DOM continua valendo.

Essa é uma diferença importante em relação ao React Native. Enquanto o React Native renderiza componentes nativos de cada plataforma, o Ionic React renderiza elementos web, o que facilita muito a criação de Progressive Web Apps.

Segundo o anúncio oficial, o objetivo era atender os desenvolvedores web que querem continuar trabalhando com as ferramentas da web. Por isso, a equipe do Ionic apostou no React com DOM, em vez de criar mais uma abstração.

Lançamento do Ionic React

O anúncio oficial do Ionic React saiu no blog do Ionic em 14 de outubro de 2019, junto com a versão 4.11 do framework. Aqui no Brasil, a novidade repercutiu no dia seguinte, 15 de outubro.

Você também pode conferir o vídeo de lançamento do Ionic React, que apresenta a proposta e os primeiros exemplos.

Para levar o app para iOS e Android, o anúncio recomenda o Capacitor, um runtime multiplataforma criado pela própria equipe do Ionic. Ainda assim, o Cordova continua sendo uma alternativa possível para quem já tem projetos com ele.

Atualizando o Ionic CLI

A primeira coisa que precisamos fazer para trabalhar com Ionic, React e TypeScript é instalar ou atualizar o Ionic CLI. Felizmente, as duas tarefas usam o mesmo comando:

npm install -g ionic

No meu caso, a versão instalada foi a 5.4.4. Para conferir a versão do seu ambiente, basta executar o comando abaixo:

ionic --version

Antes disso, você precisa ter o Node.js e o npm instalados na máquina. Se ainda não tem, o artigo sobre Node e npm: instalação, configuração e primeiros passos mostra como preparar o ambiente.

Vale lembrar também que, em versões posteriores do CLI, o pacote passou a se chamar @ionic/cli. Portanto, se você seguir a documentação mais recente, vai encontrar o comando npm install -g @ionic/cli.

Criando o app com Ionic React

Com o Ionic atualizado, basta executar o comando ionic start seguido do nome do seu app para gerar um novo projeto:

ionic start balta-courses

Durante o processo, o CLI pergunta se você deseja criar um projeto com Angular ou com React. Nesse caso, selecione React para gerar a base do Ionic React.

O CLI também oferece alguns templates iniciais, como blank, tabs e sidemenu. Para este exemplo, o template em branco já atende bem, porque vamos construir apenas uma página.

Em seguida, entre na pasta do projeto e execute ionic serve. Assim, o CLI sobe um servidor de desenvolvimento e abre o app no navegador com recarregamento automático.

Estrutura do projeto Ionic React

Ao abrir o app no Visual Studio Code, você percebe uma estrutura de pastas simples. De fato, ela lembra bastante a estrutura gerada pelo create-react-app, o conhecido CLI do React.

Essa semelhança não é coincidência. O CLI do Ionic React usa o Create React App por baixo para compilar o código e rodar o servidor de desenvolvimento.

Além disso, o projeto já vem configurado para usar TypeScript e arquivos TSX. Cada arquivo TSX reúne comportamento, estilos e marcação no mesmo lugar.

No arquivo App.tsx, temos a estrutura inicial da aplicação, com a rota que carrega a página Home.tsx:

import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import { IonApp, IonRouterOutlet } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import Home from './pages/Home';

/* CSS principal, obrigatório para os componentes do Ionic funcionarem */
import '@ionic/react/css/core.css';

/* CSS básico para apps construídos com Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';

/* Utilitários de CSS opcionais, que podem ser comentados */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';

/* Variáveis do tema */
import './theme/variables.css';

const App: React.FC = () => (
    <IonApp>
        <IonReactRouter>
        <IonRouterOutlet>
            <Route path="/home" component={Home} exact={true} />
            <Route exact path="/" render={() => <Redirect to="/home" />} />
        </IonRouterOutlet>
        </IonReactRouter>
    </IonApp>
);

export default App;

Entendendo o App.tsx

O IonApp é o componente raiz e envolve toda a aplicação. Já o IonReactRouter estende o BrowserRouter do React Router com recursos do Ionic, como transições de página com aparência nativa.

O IonRouterOutlet, por sua vez, define onde os componentes de cada rota aparecem. Nesse exemplo, a rota /home carrega a página Home, enquanto a rota raiz redireciona para /home.

Os imports de CSS também merecem atenção. O arquivo core.css é obrigatório, enquanto os utilitários de padding, alinhamento e flexbox são opcionais e podem ser removidos.

Por fim, o arquivo theme/variables.css concentra as variáveis de cor do tema. Desse modo, você personaliza a identidade visual do app sem mexer nos componentes.

Suporte nativo a TypeScript no Ionic React

Como você pode notar, o projeto usa TSX em vez de JSX. Isso significa que estamos trabalhando com o TypeScript, um superset do JavaScript que, particularmente, eu gosto bastante.

No Ionic React, o TypeScript é o padrão desde o lançamento. Ainda assim, quem preferir JavaScript puro pode renomear os arquivos e remover as anotações de tipo.

Tudo relacionado ao TypeScript já vem configurado, mas você pode modificar essas configurações facilmente no arquivo tsconfig.json.

Lembre-se de que, além da tipagem, o TypeScript permite transpilar o código para versões anteriores do ECMAScript. Ou seja, você usa recursos modernos da linguagem e entrega um código compatível com navegadores mais antigos.

Nesse projeto, o alvo da transpilação vem configurado como ES5. Assim, o código gerado roda também em navegadores mais antigos.

Se quiser revisar os recursos modernos da linguagem, o artigo Bem-vindo ao JavaScript moderno é um bom ponto de partida.

Instalando o Axios

Para este app, vamos fazer uma requisição HTTP para a API do balta.io e listar os cursos na tela. Para isso, vamos usar o Axios, um cliente HTTP baseado em Promises que funciona no navegador e no Node.js.

Sendo assim, execute o comando abaixo para instalar o pacote e registrar a dependência no package.json:

npm install axios --save

Com isso feito, abra o arquivo Home.tsx e importe o Axios no cabeçalho:

import { IonContent, IonHeader, IonPage, IonTitle } from '@ionic/react';
import React from 'react';
import axios from 'axios';

O Axios também converte automaticamente a resposta JSON em objeto. Por isso, os dados chegam prontos na propriedade data da resposta, sem a necessidade de chamar JSON.parse.

Estados com useState no Ionic React

Seguindo adiante, temos a estrutura do nosso componente. No Ionic React, uma página é simplesmente um componente de função, tipado como React.FC (Function Component).

Nele, vamos declarar uma constante com os cursos (courses) e a função que usaremos para preenchê-los (setData).

Em seguida, usamos o Hook useState para gerenciar o estado desse componente. Não esqueça de importá-lo no topo do arquivo:

import { IonContent, IonHeader, IonPage, IonTitle } from '@ionic/react';
import React, { useState } from 'react';
import axios from 'axios';

const Home: React.FC = () => {
    const [courses, setData] = useState([]);
    
    // ...
    
};

export default Home;

O useState devolve um par de valores: o estado atual e a função que o atualiza. Sempre que você chama setData, o React renderiza o componente novamente com os novos dados.

O estado começa como um array vazio. Dessa forma, a tela renderiza normalmente antes mesmo de a API responder, apenas sem nenhum curso na lista.

React Hooks e o useEffect

Com o estado definido, vamos usar o useEffect, um Hook que executa a requisição para a API assim que o componente aparece na tela. Também não esqueça de importar o useEffect no topo da página:

import { IonContent, IonHeader, IonPage, IonTitle } from '@ionic/react';
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const Home: React.FC = () => {
    const [courses, setData] = useState([]);

    useEffect(() => {
        axios.get('https://api.balta.io/v1/courses')
        .then(res => {
            setData(res.data);
        })
        .catch(err => {
            console.log(err);
        })
    }, []);

    // ...

};

export default Home;

Dentro do useEffect, fazemos a chamada à API com o Axios (axios.get) e tratamos o resultado com then ou catch.

O segundo argumento do useEffect é o array de dependências. Como ele está vazio, o React executa o efeito apenas uma vez, logo depois da primeira renderização.

No then, depois que os dados retornam, usamos o setData que definimos no estado para preencher os cursos. Então, o componente renderiza de novo, agora com a lista completa.

Tratamento de erros

No caso de erros, não estou tratando nada aqui além de registrar a mensagem no console. Contudo, fica a seu critério melhorar esse ponto.

Por exemplo, você poderia guardar a mensagem em outro estado e exibir um aviso para o usuário. Também seria possível mostrar um indicador de carregamento enquanto a requisição não termina.

Com tudo pronto, a lista de cursos obtida da API já pode ser usada na marcação do componente.

Componentes do Ionic React

Diferente do Angular, no Ionic React cada componente precisa de importação explícita. Assim, à medida que você usa os componentes, basta pressionar CTRL+. sobre eles no editor para gerar o import.

Outra diferença está no nome dos componentes. No Angular usamos ion-page, enquanto no React usamos IonPage, o que segue corretamente a convenção de nomes do React.

A estrutura de uma página no Ionic React tem um IonPage e um IonContent. Além deles, a página pode conter um IonHeader e um IonFooter, mas neste exemplo vamos focar no IonContent.

Segundo a documentação, o IonPage é o componente base de todas as páginas, porque garante as transições e o CSS correto. Já o IonContent representa a área principal de conteúdo e controla a rolagem da tela.

Listando os cursos na tela

Dentro do IonContent, vamos usar a função map do JavaScript para percorrer os cursos e exibi-los na tela:

import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonCard, IonItem, IonLabel } from '@ionic/react';
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const Home: React.FC = () => {
    const [courses, setData] = useState([]);

    useEffect(() => {
        axios.get('https://api.balta.io/v1/courses')
        .then(res => {
            setData(res.data);
        })
        .catch(err => {
            console.log(err);
        })
    }, []);

    return (
        <IonPage>
            <IonContent>
                <br />
                {courses.map((course: any) => (
                <IonCard key={course.tag}>
                    <img src={`https://baltaio.blob.core.windows.net/social/${course.tag}.png`}></img>
                    <IonItem>
                    <IonLabel>
                        <h3>{course.tag}</h3>
                        <p>{course.title}</p>
                    </IonLabel>
                    </IonItem>
                </IonCard>
                ))}
            </IonContent>
        </IonPage>
    );
};

export default Home;

Cada curso vira um IonCard com a imagem de capa, a tag e o título. Além disso, a propriedade key recebe a tag do curso, o que ajuda o React a identificar cada item da lista.

Repare também que o IonItem e o IonLabel organizam o texto dentro do card. Com isso, o app ganha a aparência típica do Ionic sem nenhuma linha extra de CSS.

Pronto, temos uma aplicação funcional, lendo dados de uma API, feita com Ionic React e TypeScript.

Próximos passos

O exemplo usa any para tipar os cursos, o que resolve o problema rapidamente. No entanto, você aproveita melhor o TypeScript criando uma interface Course com as propriedades tag e title.

Depois disso, basta tipar o estado com useState<Course[]>([]). Dessa forma, o editor passa a sugerir as propriedades corretas e acusa erros de digitação antes mesmo de rodar o app.

// Interface que descreve cada curso retornado pela API
interface Course {
    tag: string;
    title: string;
}

const [courses, setData] = useState<Course[]>([]);

Com a interface no lugar, o map não precisa mais do any. Então, o parâmetro course passa a ter o tipo Course automaticamente, por inferência do TypeScript.

Esse pequeno ajuste já mostra o valor do TypeScript em um projeto Ionic React. Afinal, quanto maior o app, mais importante fica saber exatamente o formato dos dados que circulam entre as páginas.

Também vale extrair a URL da API para uma constante ou para uma variável de ambiente. Assim, você troca o endereço entre desenvolvimento e produção sem editar o componente.

Outro passo natural é adicionar um IonHeader com um IonToolbar e um IonTitle. Assim, a página ganha uma barra superior com o nome do app.

Quando o app estiver pronto, você pode empacotá-lo com o Capacitor para gerar as versões de iOS e Android. Afinal, o mesmo código web roda no navegador, como PWA, e dentro dos apps nativos.

Se você vem do Angular, como eu, vale comparar a navegação do Ionic React com a do framework. O artigo sobre rotas, guardas e navegação no Angular ajuda a fazer essa ponte.

Conclusão

Para mim, o suporte ao React no Ionic é uma ótima novidade. Afinal, o Ionic React fortalece ainda mais o framework com o apoio da comunidade React.

Confesso que preciso me dedicar mais ao React, porque sempre foquei muito em Angular, que é o meu público principal. Porém, gostei muito do que vi com Ionic e TypeScript, e estou bastante empolgado.

Em resumo, com poucos comandos você cria o projeto, consome uma API com Axios e monta a interface com os componentes do Ionic. Portanto, se você já conhece React, a curva de aprendizado do Ionic React tende a ser bem curta.

FAQ

O Ionic React substitui o React Native?

Não, porque as propostas são diferentes. O Ionic React renderiza elementos web com react-dom, enquanto o React Native renderiza componentes nativos.

Preciso usar TypeScript com o Ionic React?

Não é obrigatório, embora o TypeScript venha como padrão. Para usar JavaScript, basta renomear os arquivos e remover as anotações de tipo.

Qual comando cria um projeto Ionic React?

O comando ionic start cria o projeto, e o CLI pergunta qual framework usar. Nesse momento, escolha React.

Como gerar o app para iOS e Android?

O caminho recomendado no lançamento é o Capacitor, que empacota o app web para as plataformas nativas.

Posso usar o Fetch em vez do Axios?

Sim, o Fetch nativo do navegador também funciona. O Axios apenas simplifica algumas tarefas, como a conversão automática do JSON.