React Native e TypeScript
Melhorando a manutenção do seu aplicativo
Sempre que nossa aplicação começa a crescer em funcionalidades e em equipe. A confiança na base de código tende a diminuir com o tempo. Mesmo com uma boa cobertura de testes, aquela pulga atrás da orelha vem e vai.
Um maneira de melhorar isso, é utilizar tipagem estática. O padrão do React Native é o FlowType, mas nesse artigo, iremos ver como podemos utilizar TypeScript para o nosso aplicativo.
Antes de começar
Não irei falar sobre os requesitos básicos para instalar React Native na sua máquina. Para isso, eu recomendo você ir na documentação oficial e seguir o “Getting Started”.
Inicializando um projeto
Com a linha de comando do React Native instalada:
npm install --global react-native-cli
Nós iremos iniciar nosso projeto:
react-native init Projeto
Ao verificar os arquivos instalados, você terá algo como:
.babelrc
.buckconfig
.flowconfig
.gitattributes
.gitignore
.watchmanconfig
App.js
__tests__
android
app.json
index.js
ios
node_modules
package-lock.json
package.json
Mudando a estrutura de projeto
As primerias etapas que iremos realizar são:
- Criar uma pasta
src
- Mover a pasta
__tests__
parasrc
- Mover
index.js
eApp.js
parasrc
Algo como:
mkdir src
mv __tests__ index.js App.js src/
O arquivo de entrada do React Native é um index.js
localizado na raíz do seu projeto. Porém, nós acabamos de move-lo para src
, como o projeto irá funcionar?
A idéia aqui é fazer o TypeScript compilar toda a pasta src
e enviar para uma pasta de saída, vamos chamar ela de compiled
(você pode configurar qualquer nome).
Para isso, esse arquivo de entrada precisará apontar para nossa pasta compiled
ao invés de src
.
Iremos criar outro index.js
e adicionar um import
para os arquivos compilados pelo TypeScript.
// na raíz do seu projeto
vim index.js
E adicione:
import "./compiled";
E acredito que você tenha aprendido a sair do vim
😁
Com isso, nossa estrutura final é:
.babelrc
.buckconfig
.flowconfig
.gitattributes
.gitignore
.watchmanconfig
android
app.json
index.js // novo arquivo importando "./compiled"
ios
node_modules
package-lock.json
package.json
src // nova pasta com App.js index.js e __tests__
Deletando arquivos desncessários
O único arquivo que está perdido nessa nova estrutura, é o .flowconfig
.
Acho que você já sabe o que fazer! 🔥, ficamos com:
.babelrc
.buckconfig
.gitattributes
.gitignore
.watchmanconfig
android
app.json
index.js
ios
node_modules
package-lock.json
package.json
src
Adicionando TypeScript
Agora, vamos instalar o pacote principal typescript
:
npm install --save typescript
E no nosso package.json
iremos adicionar o seguinte script
:
...
"scripts": {
"tsc:w": "tsc --watch",
....
}
...
Com isso, nós podemos deixar o compilador do TypeScript rodando e habilitar Live Reload/Hot Reloading em desenvolvimento no emulador do React Native.
Vamos criar nosso tsconfig.json
, para dizer ao TypeScript, como e onde ele deve compilar nossos arquivos.
A configuração a seguir, é bem básica, é o mínimo necessário. Como algumas regras são discutidas por time e projeto. Não vou perder muito tempo aqui, mas aí vai:
{
"compilerOptions": {
"target": "es2015",
"module": "es2015",
"moduleResolution": "node",
"jsx": "react-native",
"outDir": "compiled",
"rootDir": "src"
},
"include": ["src/**/*"],
"exclude": [
"android",
"ios",
"compiled",
"node_modules"
]
}
Renomeando arquivos .js para .ts(x)
Mais uma etapa, é renomear nossos arquivos em src
de .js
para .ts
ou .tsx
.
src/index.js
=>src/index.tsx
src/App.js
=>src/App.tsx
É preciso também mudar o conteúdo de App.tsx
, recomendo dar uma olhada nos exemplos React/React Native na documentação oficial do TypeScript caso esse mundo seja novo para você.
Antes:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View
} from 'react-native';const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' +
'Cmd+D or shake for dev menu',
android: 'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});export default class App extends Component<{}> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit App.js
</Text>
<Text style={styles.instructions}>
{instructions}
</Text>
</View>
);
}
}const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
Depois:
import * as React from "react";
import { Platform, StyleSheet, Text, View } from "react-native";const instructions = Platform.select({
ios: "Press Cmd+R to reload,\n" + "Cmd+D or shake for dev menu",
android:
"Double tap R on your keyboard to reload,\n" +
"Shake or press menu button for dev menu"
});export default class App extends React.Component<{}, {}> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
</View>
);
}
}const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#F5FCFF"
},
welcome: {
fontSize: 20,
textAlign: "center",
margin: 10
},
instructions: {
textAlign: "center",
color: "#333333",
marginBottom: 5
}
});
src/__tests__/App.js
=>src/__tests__/App.tsx
O conteúdo também será alterado,
Antes:
import 'react-native';
import React from 'react';
import App from '../App';// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';it('renders correctly', () => {
const tree = renderer.create(
<App />
);
});
Depois:
import "react-native";
import * as React from "react";
import App from "../App";// Note: test renderer must be required after react-native.
import * as renderer from "react-test-renderer";it("renders correctly", () => {
const tree = renderer.create(<App />);
});
Adicionando @types
Por hora, nossas depêndencias são pequenas, então, iremos precisar apenas de 3 pacotes:
npm install @types/jest @types/react @types/react-native
Devido a um conflito entre @types/node
e @types/react-native
⚠️, precisamos alterar nosso tsconfig.json
e incluir um array definindo quais types
estamos utilizando:
{
"compilerOptions": {
...
"types": ["react", "react-native", "jest"]
}
}
Atualizando a configuração do Jest
Aqui a mudança é bem pequena, no seu package.json
, adicione a chave testRegex
para indicar ao Jest, onde ele deve ir buscar os arquivos de teste (para evitar que ele faça a leitura de todos os diretórios etc).
No seu package.json
:
...
"jest": {
...
"testRegex": "compiled/__tests__/.*"
}
...
Verificando se tudo deu certo 🎉
Vale a pena deixar o compilador do TypeScript rodando em uma aba do seu terminal:
npm run tsc:w
Com isso, podemos executar nossos testes:
npm run test
Iremos utilizar o simulador do iOS no nosso exemplo.
Vale lembrar, que a linha de comando do React Native, react-native run-*
, irá verificar se tem algum Metro Bundler rodando (React Native Packager), e, caso nenhum seja detectado, ele irá iniciar uma sessão automaticamente, abrindo uma nova janela do seu terminal (não se assuste!).
Isso evita a necessidade de manualmente rodar npm start
em outra aba.
react-native run-ios
Finalizando
Essa é uma configuração bem simples e manual. Funciona muito bem dependendo do tamanho do aplicativo que você vai criar.
Você também pode usar Live Reload/Hot Reloading, basta ativa-lo no simulador:
Para ter algo mais organizado, recomendo o Haul, uma linha de comando que substitui a do React Native e expõe um arquivo de configuração do webpack para o seu projeto.
Ele até permite deletar o arquivo.watchmanconfig
, pois como utilizaremos o webpack para compilação, não há necessidade dessa outra dependência.
Quem sabe eu falo sobre ele em um próximo artigo📱🚀👻