Fala Samurai, blz?

Nesse artigo vamos introduzir uma ferramenta que permite adicionar tipagem e alguns recursos adicionais a linguagem JavaScript possibilitando melhorar a estrutura dos projetos em React Native e deixar o código escalável para facilitar a manutenção futura.

Quando iniciamos nossos projetos em React Native é comum seguir codificando sem preocupações como “qual o tipo da variável X” ou “oque a função Y vai retornar” ou se não vai retornar nada e só vai fazer alguma coisa, etc. E é natural, afinal, o Javascript permite que nossos programas sejam escritos com tipagem dinâmica, isso quer dizer que as variáveis e funções podem assumir tipos diferentes no decorrer da execução do código.

E isso pode parecer favorecer na produtividade, ou seja, na medida em que vamos escrevendo o código sem nos preocuparmos com a tipagem perdemos menos tempo.

Porém, existem situações em que essa possibilidade de escrever o código de forma dinâmica pode gerar outros problemas e essa aparência de produtividade acaba caindo por água abaixo e então vai valer a pena pensar em trabalhar com tipagem estática e é sobre isso que vamos falar nesse artigo ao introduzir o uso de Typescript com React Native.

O que é TypeScript e por que utilizar?

O TypeScript não é uma linguagem de programação e sim uma ferramenta criada pela Microsoft com o intuito de resolver alguns problemas relacionados a tipagem naturalmente dinâmica da linguagem JavaScript.

Imagine um projeto escrito em React Native em que você não mexe já faz algum tempo e precisa realizar uma manutenção ou ajuste no código.

Estando ele escrito com tipagem dinâmica, você irá precisar entender o que as funções estão retornando ou se só estão fazendo alguma coisa e de que tipo são as variáveis do código, com que valor elas iniciaram e depois se assumiram algum outro valor, etc.

Também pode acontecer que outra pessoa da equipe vá mexer no seu código e por algum motivo não tenha a mesma linha de raciocínio que você teve ao nomear as variáveis e pode pensar que a variável X que estava trabalhando com números agora possa receber um texto ou simplesmente faz isso porque foi mais fácil ali na hora de mexer e a linguagem permite.

No primeiro caso isso pode te custar mais tempo e no segundo caso pode causar erros no futuro ou confusão e uma estrutura ruim de código.

Nos dois casos temos um problema que pode ser resolvido com padronização de código e utilização do TypeScript.

Tipagem

Quando falamos de tipagem no TypeScript queremos dizer que ao declarar uma variável ou função definimos um tipo para ela e que ela vai assumir esse tipo durante todo o ciclo de vida em tempo de execução do código, ex de declaração:

let decimal: number = 6;
let color: string = "blue";

E vai acusar um erro ao tentar definir um valor diferente de number para a variável decimal ou um valor diferente de string para a variável color.

Os tipos possíveis são:

  • Boolean;
  • Number;
  • String;
  • Array;
  • Tuple;
  • Enum;
  • Any;
  • Void;
  • Null and Undefined;
  • Never;
  • Object;

Usando TypeScript no React Native

Para usar o TypeScript com React Native você pode começar um novo projeto já pré configurado com o comando:

npx react-native init MyTSProject --template react-native-template-typescript@6.2.0

Ou pode utilizar em um projeto já existente fazendo algumas instalações e modificações necessárias.

Para o nosso exemplo, iremos utilizar um projeto já existente que pode ser baixado nesse repositório do github da Dev Samurai:

Link para o SamuraiCam no Github

Se quiser saber como criar esse projeto do zero, acesse nosso tutorial do SamuraiCam:

React Native Tutorials: Usando a Câmera do Celular

Bom, com o projeto em mãos, a primeira coisa a fazer depois de rodar o yarn é instalar as dependências necessárias para utilizar o Typescript:

yarn add typescript @types/jest @types/react @types/react-native @types/react-test-renderer

Depois precisamos criar 2 arquivos na raiz do projeto para configurar as dependências do TypeScript.

Um deles é tsconfig.json:

{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "jsx": "react",
    "lib": ["es6"],
    "moduleResolution": "node",
    "noEmit": true,
    "strict": true,
    "target": "esnext"
  },
  "exclude": [
    "node_modules",
    "babel.config.js",
    "metro.config.js",
    "jest.config.js"
  ]
}

O outro é jest.config.js:

module.exports = {
preset: “react-native”,
moduleFileExtensions: [“ts”, “tsx”, “js”, “jsx”, “json”, “node”]
};

Beleza, agora todo arquivo do projeto que desejamos utilizar o TypeScript precisa ser renomeado para a extensão .tsx e no nosso exemplo só temos o App.js, então vamos renomear para App.tsx.

Precisamos agora adaptar o código para que seja um arquivo TypeScript válido.

Se rodar o comando yarn tsc ele vai verificar os arquivos .tsx e irá apontar os erros encontrados.

Nosso código vai retornar 7 erros. No console você verá os erros encontrados, os locais dos arquivos e no final o total que deve estar assim Found 7 errors.

A ideia é corrigir esses erros para que o código fique padronizado com o TypeScript.

Os primeiros erros notados são em dois imports no começo do arquivo:

import Icon from 'react-native-vector-icons/MaterialIcons'; e import logo from './assets/logo.png';

Dizendo que esses módulos não foram encontrados, então nós vamos criar um arquivo chamado declarations.d.ts na raiz do projeto em que podemos centralizar a declaração dos módulos necessários:

declare module 'react-native-vector-icons/MaterialIcons';
declare module '*.png';

Outro erro que aparece são com as props do componente Camera, pois, não temos uma tipagem definida para aquelas props.

E como temos 3 tipos de props, podemos criar uma interface para definir os tipos das propriedades recebidas pelo Camera.

No arquivo declarations.d.ts adicione:

interface CameraProps {
  isVisible: boolean;
  onChangePhoto(uri: string): void;
  onCloseCamera(): void;
}

Ou seja, criamos uma interface chamada CameraProps que possuí 3 valores:

  • isVisible é uma variável que deve receber um valor booleano;
  • onChangePhoto é uma função que deve receber sempre um parâmetro uri do tipo string e ela não retorna nenhum valor (void);
  • onCloseCamera é uma função que não recebe nenhum parâmetro e não retorna nada (void);

Agora la no App.tsx vamos definir que as props de Camera são do tipo CameraProps:

const Camera = ({isVisible, onChangePhoto, onCloseCamera}: CameraProps) => {...}

Depois, no componente App temos a função onChangePhoto que recebe um parâmetro newPhoto e ele deve ser tipado como string, pois, definimos lá no CameraProps que o método onChangePhoto deve receber um parâmetro string e é essa função que estamos enviando nas props do componente Camera.

const [photo, setPhoto] = useState();

const onChangePhoto = (newPhoto: string) => {
  setPhoto(newPhoto);
  setIsCameraVisible(false);
};

No final nosso App.tsx deve estar assim:

import React, { useState } from "react";
import {
  Alert,
  Modal,
  View,
  Image,
  ImageBackground,
  TouchableOpacity,
  StyleSheet
} from "react-native";

import Icon from "react-native-vector-icons/MaterialIcons";
import { RNCamera } from "react-native-camera";

import logo from "./assets/logo.png";

const Camera = ({ isVisible, onChangePhoto, onCloseCamera }: CameraProps) => {
  const [camera, setCamera] = useState();

  const onTakePicture = async () => {
    try {
      const { uri } = await camera.takePictureAsync({
        quality: 0.5,
        forceUpOrientation: true,
        fixOrientation: true,
        skipProcessing: true
      });
      onChangePhoto(uri);
    } catch (error) {
      Alert.alert("Erro", "Houve um erro ao tirar a foto.");
    }
  };

  return (
    <Modal animationType="slide" transparent={false} visible={isVisible}>
      <RNCamera
        ref={ref => setCamera(ref)}
        style={{ flex: 1 }}
        type={RNCamera.Constants.Type.back}
        autoFocus={RNCamera.Constants.AutoFocus.on}
        flashMode={RNCamera.Constants.FlashMode.off}
        androidCameraPermissionOptions={{
          title: "Permissão para usar a câmera",
          message: "Precisamos da sua permissão para usar a câmera.",
          buttonPositive: "Ok",
          buttonNegative: "Cancelar"
        }}
        captureAudio={false}
      >
        <Icon
          name="photo-camera"
          size={40}
          color={"#fff"}
          onPress={onTakePicture}
          style={styles.buttonTakePicture}
        />
        <Icon
          name="close"
          size={50}
          color={"#fff"}
          onPress={onCloseCamera}
          style={styles.buttonCloseCamera}
        />
      </RNCamera>
    </Modal>
  );
};

const App = () => {
  const [isCameraVisible, setIsCameraVisible] = useState(false);
  const [photo, setPhoto] = useState();

  const onChangePhoto = (newPhoto: string) => {
    setPhoto(newPhoto);
    setIsCameraVisible(false);
  };

  const onCloseCamera = () => {
    setIsCameraVisible(false);
  };

  return (
    <View style={styles.container}>
      <Image source={logo} style={styles.logo} />
      <View style={styles.photo}>
        <ImageBackground
          style={{ width: "100%", height: "100%" }}
          source={{ uri: photo }}
        />
      </View>
      <View style={styles.buttons}>
        <TouchableOpacity
          style={styles.button}
          onPress={() => {
            setIsCameraVisible(true);
          }}
        >
          <Icon name="camera-alt" size={40} color={"#f37272"} />
        </TouchableOpacity>
        <TouchableOpacity
          style={styles.button}
          onPress={() => {
            setPhoto(null);
          }}
        >
          <Icon name="delete" size={40} color={"#f37272"} />
        </TouchableOpacity>
      </View>
      <Camera
        isVisible={isCameraVisible}
        onChangePhoto={onChangePhoto}
        onCloseCamera={onCloseCamera}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#f37272"
  },
  logo: {
    alignSelf: "center",
    marginTop: 60
  },
  photo: {
    width: 300,
    height: 200,
    backgroundColor: "#fff",
    alignSelf: "center",
    marginTop: 80
  },
  buttons: {
    marginTop: 10,
    flexDirection: "row",
    justifyContent: "center"
  },
  button: {
    backgroundColor: "#fff",
    margin: 20,
    borderRadius: 150,
    width: 80,
    height: 80,
    alignItems: "center",
    justifyContent: "center"
  },
  buttonTakePicture: {
    flex: 0,
    alignSelf: "center",
    position: "absolute",
    bottom: 20
  },
  buttonCloseCamera: {
    flex: 0,
    position: "absolute",
    top: 20,
    right: 20
  }
});

export default App;

E ao rodar o comando yarn tsc novamente para verificar os erros a mensagem deve estar parecida com essa: Done in 4.28s.

Conclusão

Utilizar o TypeScript tem diversas vantagens que podem não parecer fazer sentido para quem não está acostumado, pois, tem uma média curva de aprendizado para entender as especificações e as formas corretas de se utilizar a tipagem.

Mas é interessante conhecer e utilizar, pois, em projetos que começam a crescer em escopo ou equipe, a padronização e alinhamento entre os devs acabada se tornando crucial e aplicar tipagem e os recursos de orientação a objetos oferecidos pelo TypeScript é uma excelente escolha.

Nesse artigo tivemos apenas uma introdução básica sobre o assunto mas pretendemos criar um projeto do zero em React Native utilizando TypeScript com as melhores práticas, por isso fica de olho nos anúncios da nossa comunidade la no Discord:

Comunidade Dev Samurai no Discord

O código de exemplo utilizado aqui veio do projeto SamuraiCam que está disponível para download nesse repositório:

Link do Github para o SamuraiCam

E também temos um tutorial bem legal, ensinado o passo a passo para criar esse projeto (SamuraiCam) do zero:

React Native Tutorials: Usando a Câmera do Celular

E para você não ficar de fora dos nossos artigos e tutoriais sobre programação, carreira e desenvolvimento de aplicativos, cadastre seu e-mail e receba os anúncios fresquinhos das novidades aqui do blog

Valeu Samurai por acompanhar esse artigo e até a próxima.