Convertendo React em TypeScript

Melhorando a manutenção do seu código em longo prazo

Protegendo o seu futuro eu dos bugs de agora e os da semana que vem!

Em qualquer projeto que precisar de manutenção constante, eu normalmente prefiro usar TypeScript ao invés de JavaScript. Considerando que boa parte do meu tempo eu estou focado em criar projetos para web com React, eu acabo combinando os dois — e eles funcionam muito bem!

Assim como boa parte das bibliotecas hoje em dia, os arquivos com definições do React para TypeScript estão disponíveis e sempre atualizados!

Não tenha medo caso algo que você gosta de usar pare de ser suportado — no pior dos casos, é só JavaScript.

JavaScript

Vamos pegar um exemplo comum, um componente React genérico escrito em ES6 transpilado com Babel:

import React from 'react';
import PropTypes from 'prop-types';
class Input extends React.Component {
  constructor(props) {
super(props);
this.state = {
value: '',
};
this.handleChange= this.handleChange.bind(this);
}
  handleChange(event) {
this.setState({ value: event.currentTarget.value });
}
  render() {
return (
<div className="input__container">
<span>{this.props.label}</span>
<input value={this.state.value} onChange={this.handleChange} />
</div>
);
}
}
Input.propTypes = {
label: PropTypes.string.isRequired,
};
export default Input;

Temos um componente criado através de uma classe com estado, que envolve um campo de texto, bem simples.

Agora, vamos converter isso em TypeScript e ver a diferença:

import * as React from 'react';
export interface IProps {
label: string;
}
export interface IState {
value: string;
}
class Input extends React.Component<IProps, IState> {
  constructor(props: IProps) {
super(props);
this.state = {
value: '',
};
this.handleChange = this.handleChange.bind(this);
}
  private handleChange(event: React.FormEvent<HTMLInputElement>): void {
this.setState({ value: event.currentTarget.value });
}
  public render(): JSX.Element {
return (
<div className="input__container">
<span>{this.props.label}</span>
<input value={this.state.value} onChange={this.handleChange} />
</div>
);
}
}
export default Input;

Nada muito diferente, porém, podemos dizer que está mais verboso! A estrutura é bem parecida com JavaScript, então, fica fácil de ler para quem já trabalhou com React antes.

Vamos quebrar esse exemplo em partes e explicar as diferenças:

Imports

No nosso exemplo em JavaScirpt/ES6, nós usamos import React from ‘react’, mas em TypeScript, nós adicionamos o "* as React". Essa diferença é derivada da forma com que o ES Modules funciona.

O import React from ‘react’ está importando um objeto exportado chamado default — parecido com o que é definido no final do nosso primeiro exemplo.

O próprio React usa module.exports ao invés de ES Modules. Técnicamente falando, de acordo com a especificação, a implementação do Babel é incorreta. Babel tenta interpretar o valor desse import como válido, apenas por conveniência. No seu arquivo tsconfig, existe uma opção similar, chamada allowSyntheticDefaultImports, que disponibiliza a mesma funcionalidade.

Usamos “* as React” para demonstrar como a configuração padrão do TypeScript funciona.

IProps e IState

IProps e IState são interfaces — eles definem o formato de um objeto JavaScript. Ao definir esses formatos, nos declaramos eles como sendo nossa props e state do nosso componente. Isso é similar e substitui o .propTypes — a diferença é que o módulo, prop-types, também valida em tempo de execução (no navegador), TypeScript faz o mesmo, mas em tempo de compilação (enquanto você modifica seu código).

Se você viu no exemplo, eles também são exportados, ajudando a definir testes para os componentes.

private e public

Antes de analisarmos a função handleChange, você deve ter notado as palavras chaves private. Elas dizem ao TypeScript que, se essa classe for importada em outro arquivo, esses métodos não estarão disponíveis para uso. Eles são apenas para uso interno.

Por exemplo, eu não posso fazer:

const input = new Input();
input.handleChange(undefined);

Isso é ótimo, apenas, para uma melhor experiência no desenvolvimento do código e também, na sua futura manutenção — ultimamente, TypeScript compila para JavaScript e aquele método estará no .prototype do objeto, ou seja, no final, nada impede aquela função de ser chamada.

No método render, nós temos public — Isso significa que esse método pode ser chamado após a classe ter sido inicializada (apesar de que, na prática, você não fará isso manualmente). Se nada for especificado, public é usado como padrão no compilador do TypeScript.

handleChange

A função permanece a mesma — recebe um evento e atualiza o estado interno. A diferença está na declaração dos tipos de objetos — React tem uma gama de interfaces para cada tipo de evento. E também estamos declarando o void, que significa que essa função não retorna nada.

E o que ganhamos com isso? Se o seu editor tem integração com TypeScript, por exemplo, o VSCode, nós temos um ótimo autocomplete de propriedades:

O próximo benefício, é com a validação de erros. Vamos dizer que o usuário não quer um campo de texto, ele quer um menu dropdown. Se nós mudarmos o input por select, teremos, em tempo real, o seguinte erro no nosso console:

TS2322: Type '(event: FormEvent<HTMLInputElement>) => void' is not assignable to type 'EventHandler<ChangeEvent<HTMLSelectElement>>'.

Isso é ótimo porque está nos mostrando que existe outra parte do código que está dependendo do input — e você é alertado enquanto modifica seu código e não quando você está testando no navegador e não sabe porque aquilo não está funcionando.

render

Nós adicionamos um tipo de retorno, chamado JSX.Element. Se você já, acidentalmente, retornou outra coisa na sua função render que não seja JSX, você recebe um erro grotesco chamado InvariantViolation no console do seu navegador. Declarando os tipos, você descobre o erro bem antes de chegar nessa etapa!

Conclusão

E chegamos no final dessa conversa, nós vimos que ganhamos uma boa ajuda para achar bugs, como se estive validando seu código em tempo real, e adicionamos uma camada que facilita a manutenção futura, que ainda não é possível só com JavaScript.

Porém, isso não vem de graça! O código fica mais verboso e um pouco maior, e ainda temos o custo de fazer essa conversão. Se você tem um projeto existente, e ele é grande, essa é uma decisão que você e sua equipe tem que tomar para ver se realmente vale a pena.

Se você quiser compartilhar sua experiência com TypeScript, React ou tiver algo bacana para adicionar, só comentar abaixo! É sempre bom ouvir a perspective de outras pessoas!

Crédito