React TypeScript noções básicas e práticas recomendadas

Não existe uma maneira “certa” única de escrever o código React usando o TypeScript.
Como em outras tecnologias, se o seu código compilar e funcionar, você provavelmente fez algo certo.

Dito isto, existem “práticas recomendadas” que você gostaria de considerar a seguir, especialmente ao escrever código, outras pessoas terão que ler ou reutilizar para seus próprios propósitos.

Então, aqui vou listar alguns trechos de código úteis que seguem as "melhores práticas". Existem muitos deles, alguns que você já pode ter usado anteriormente e outros que podem ser novos. Basta percorrer a lista e fazer anotações mentais. Marcar este artigo como referência futura também pode ser uma boa ideia.

Ao criar projetos usando o React com TS, você garante que seus componentes sejam facilmente compreensíveis para outros desenvolvedores (assim como para o seu futuro). Isso é absolutamente crucial para prepará-los para o compartilhamento. É uma ótima maneira de escrever código sustentável e otimizar a colaboração da equipe.

Começando

create-react-app com TypeScript

$ npx create-react-app seu-nome-do-aplicativo --template typescript

Se você não é um fã de Yarn, pode usar o seguinte comando:

$ yarn create react-app seu-nome-do-aplicativo --template typescript

Nos dois casos, observe como não estamos usando o aplicativo diretamente. Em vez disso, estamos usando outras ferramentas que farão o download da versão mais recente do aplicativo sempre que necessário. Isso ajuda a garantir que você não esteja usando uma versão desatualizada.

Fundamentos

Alguns dos petiscos muito interessantes adicionados por TS ao idioma são:
Interfaces.

Um dos muitos benefícios que o TypeScript traz para a tabela é o acesso a construções como essa, que permitem definir a interface de seus componentes ou até mesmo qualquer outro objeto complexo que você queira usar com eles, como a forma como o Props objeto será. possui (ou seja, quantas propriedades e seus tipos).

import React from 'react';

interface IButtonProps {
    /** The text inside the button */
    text: string,
    /** The type of button, pulled from the Enum ButtonTypes */
    type: ButtonTypes,
    /** The function to execute once the button is clicked */
    action: () => void
}

const ExtendedButton : React.FC<IButtonProps> = ({text, type, action}) => {

}

O código acima garante que quem usa seus componentes precise adicionar exatamente 3 propriedades:

  • texto: que precisa ser uma String
  • type: que precisa ser uma opção ButtonType (abordarei Enums em um segundo)
  • ação: que é uma função simples

Observe que se estender FC (Componente Funcional) com nossa própria interface personalizada. Isso fornece à nossa função todas as definições genéricas de componentes funcionais, como a prop 'filhos' e um tipo de retorno que deve ser atribuível a JSX.Element.

Se você ignorar um deles ou enviar algo que não é compatível, o compilador TypeScript e o seu IDE (assumindo que você esteja usando um IDE específico para JavaScript, como Código) o notificarão e não permitirão que você continue até que você o corrija . .

Uma maneira melhor de definir nosso elemento ExtendedButton seria estender um tipo de elemento de botão HTML nativo da seguinte forma:

import  React ,  { ButtonHTMLAttributes }  de  'react' ;

interface  IButtonProps  estende  ButtonHTMLAttributes < HTMLButtonElement >  {
    / ** O texto dentro do botão * /
    texto : string ,
    / ** O tipo de botão, extraído dos Enum ButtonTypes * /
    tipo : ButtonTypes ,
    / ** A função a ser executada quando o botão é clicado * /
    ação : ( )  =>  nulo
}

const  ExtendedButton : React . FC < IButtonProps >  =  ( { texto , tipo , ação } )  =>  {

}

Além disso, observe que, ao trabalhar com Bit.dev ou react -docgen, a seguinte sintaxe é necessária para gerar automaticamente documentos :

const  ExtendedButton : React . FC < IButtonProps >  =  ( { texto , tipo , ação } : IButtonProps )  =>  {

}

(Os adereços são definidos direta e explicitamente :IButtonProps, além de definir o componente com :React.FC)

Enums

Assim como nas Interfaces, as Enums permitem definir um conjunto de constantes relacionadas como parte de uma única entidade.

//...
/** A set of groupped constants */
enum SelectableButtonTypes {
    Important = "important",
    Optional = "optional",
    Irrelevant = "irrelevant"
}

interface IButtonProps {
    text: string,
    /** The type of button, pulled from the Enum SelectableButtonTypes */
    type: SelectableButtonTypes,
    action: (selected: boolean) => void
}

const ExtendedSelectableButton = ({text, type, action}: IButtonProps) => {
    let [selected, setSelected]  = useState(false)

    return (<button className={"extendedSelectableButton " + type + (selected? " selected" : "")} onClick={ _ => {
        setSelected(!selected)
        action(selected)
    }}>{text}</button>)
}

/** Exporting the component AND the Enum */
export { ExtendedSelectableButton, SelectableButtonTypes}

Observe que, diferentemente de Interfaces ou Tipos, o Enums será traduzido para JavaScript simples. Então, por exemplo, isso:

enum SelectableButtonTypes {
Important = "important",
Optional = "optional",
Irrelevant = "irrelevant"
}

vai se transformar nisso:

"use strict"; 
var SelectableButtonTypes; 
(function (SelectableButtonTypes) { 
SelectableButtonTypes ["Important"] = "important"; 
SelectableButtonTypes ["Opcional"] = "opcional"; 
SelectableButtonTypes ["Irrelevant"] = "irrelevante"; 
}) (SelectableButtonTypes || (SelectableButtonTypes || (SelectableButtonTypes || ));

Alias de interfaces vs tipos

Uma pergunta comum que os novatos no TypeScript têm é se devem usar Interfaces ou Aliases de tipo para diferentes partes do código - afinal, a documentação oficial é um pouco incerta sobre esse tópico.

A verdade é que, embora essas entidades sejam conceitualmente diferentes, na prática, são bastante semelhantes:

  1. Ambos podem ser estendidos.
// estendendo interfaces
interface  PartialPointX  {  x : número ;  }
interface  Point  estende o  PartialPointX  {  y : number ;  }

// tipos de extensão
tipo  PartialPointX  =  {  x : number ;  } ;
tipo  Point  =  PartialPointX & {  y : number ;  } ;

// A interface estende o tipo 
tipo  PartialPointX  =  {  x : number ;  } ;
interface  Point  estende o  PartialPointX  {  y : number ;  }

// O alias de tipo estende interfaces
interface  PartialPointX  {  x : número ;  }
tipo  Point  =  PartialPointX & {  y : number ;  } ;
  1. Ambos podem ser usados para definir a forma dos objetos.
// definindo a interface para objetos
 ponto de  interface {
  x : número ;
  y : número ;
}

// usando tipos também
tipo  Point2  =  {
  x : número ;
  y : número ;
} ;
  1. Ambos podem ser implementados da mesma maneira.
// implementando a interface
classe  SomePoint  implementa  Point  {
  x : 1 ;
  y : 2 ;
}

// Implementando o alias de tipo
classe  SomePoint2  implementa  Point2  {
  x : 1 ;
  y : 2 ;
}

tipo  PartialPoint  =  {  x : number ;  } | {  y : número ;  } ;

// Essa é a única coisa que você não pode fazer: implementar um tipo de união
classe  SomePartialPoint  implementa  PartialPoint  {
  x : 1 ;
  y : 2 ;
}

O único recurso extra que as interfaces trazem para a tabela (que os aliases de tipo não fazem) é a " mesclagem de declarações", o que significa que você pode definir a mesma interface várias vezes e, com cada definição, as propriedades são mescladas:

Tipos opcionais para seus acessórios

Parte dos benefícios do uso do Interfaces é que você pode impor as propriedades de seus acessórios para seus componentes. No entanto, graças à sintaxe opcional disponível no TypeScript, você também pode definir acessórios opcionais, como este:

// ...
 IProps da  interface {
  prop1 : string ,
  prop2 : número , 
  myFunction : ( )  =>  nulo ,
  prop3 ?: boolean  // prop opcional
}

// ...
função  MyComponent ( { ... props } : IProps )  {
  // ...
}

/ ** Você pode usá-los assim * /
< mycomponent  prop1 = "text here"  prop2 = 404  myFunction = { ( )  =  {
  // ...
} } / >

< mycomponent  prop1 = "text here" prop2 = { 404 } minhaFunção = { ( )  =  {
  // ...
} }   prop3 = { false } / >

Hooks

Hooks são a nova mecânica que o React fornece para interagir com vários de seus recursos (como o estado) sem a necessidade de definir uma classe.

Adicionando verificação de tipo aos hooks

hooks como useState recebem um parâmetro e retornam corretamente o estado (novamente, neste caso) e uma função para defini-lo.

Graças à validação de tipo do TypeScript, você pode aplicar o tipo (ou interface) do valor inicial do estado, assim:

const  [usuário, setUser]  =  Reagir . useState < IUser > (user) ;

Valores anuláveis para hooks

No entanto, se o valor inicial do seu hook puder ser potencialmente um null, o exemplo acima falhará. Nesses casos, o TypeScript permite definir também um tipo opcional, garantindo que você esteja coberto por todos os lados.

Dessa forma, você garante que mantém as verificações de tipo, mas permite os cenários em que o valor inicial pode ser o mesmo null.

Componentes genéricos

Assim como você define funções e interfaces genéricas no TypeScript, você pode definir componentes genéricos, permitindo reutilizá-los para diferentes tipos de dados. Você também pode fazer isso para adereços e estados.

interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>(props: Props<T>) {
  const { items, renderItem } = props;
  const [state, setState] = React.useState<T[]>([]); 

  return (
    <div>
      {items.map(renderItem)}
    </div>
  );
}

Você pode usar o componente aproveitando a inferência de tipo ou especificando diretamente os tipos de dados, assim:

ReactDOM.render (
  < Lista
    items = { [ "a", "b" ] }  // tipo de 'string' inferido aqui
    renderItem = { item  =>  (
      < li  key = { item } >
        { item . trim ( ) } // permitido, porque estamos trabalhando com 'strings' ao redor
      < / li >
    ) }
  / > ,
  documento . corpo
) ;

Exemplo de inferência de tipo

ReactDOM . render (
  < Lista < número >
    itens = { [ 1 , 2 , 3 , 4 ] } 
    renderItem = { item  =>  < li  chave = { item } > { item . toPrecision ( 3 ) } < / li > }
  / > ,
  document.body
);

Para o último, observe que, se sua lista conter cadeias de caracteres em vez de números, o TypeScript gerará um erro durante o processo de transpilação.

Estendendo elementos HTML

Às vezes, seus componentes funcionam e se comportam como elementos HTML nativos (em esteróides). Por exemplo, uma "caixa entediada" (que é simplesmente um componente que sempre renderiza um div com uma borda padrão) ou um "envio grande" (que novamente não passa de um bom botão de envio antigo com um tamanho padrão e talvez com algum comportamento personalizado )

Para esses cenários, é melhor definir o tipo de componente como um elemento HTML nativo ou uma extensão dele.

export interface IBorderedBoxProps extends React.HTMLAttributes<HTMLDivElement> {
    title: string;
}

class BorderedBox extends React.Component<IBorderedBoxProps, void> {
    public render() {
        const {children, title, ...divAttributes} = this.props;

        return (
            //it is a DIV afterall, and we're trying to let the user use this component knowing that.
            <div {...divAttributes} style={{border: "1px solid red"}}>
                <h1>{title}</h1>
                {children}
            </div>
        );
    }
}

const myBorderedBox = <BorderedBox title="Hello" onClick={() => alert("Hello")}/>;

Como você pode ver, estendi os adereços padrão do HTML e adicionei um novo: "title" para minhas necessidades específicas.

Tipos de Eventos

Como você provavelmente sabe, o React fornece seu próprio conjunto de eventos, e é por isso que você não pode usar diretamente os bons e antigos eventos HTML. Dito isso, você tem acesso a todos os eventos úteis da interface do usuário de que precisa, tanto que, na verdade, eles também têm os mesmos nomes. Portanto, React.Mouse Eventlembre -se de referenciá-los diretamente ou lembre-se de importá-los do React dessa maneira:

import React, {Component, MouseEvent} de 'react';

Os benefícios de usar o TypeScript aqui é que também podemos usar Genéricos (como no exemplo anterior) para restringir os elementos nos quais um manipulador de eventos específico pode ser usado.

Por exemplo, o seguinte código não funcionará:

function eventHandler(event: React.MouseEvent<HTMLAnchorElement>) {
    console.log("TEST!")
}

const ExtendedSelectableButton = ({text, type, action}: IButtonProps) => {

    let [selected, setSelected]  = useState(false)

    return (<button className={"extendedSelectableButton " + type + (selected? " selected" : "")} onClick={eventHandler}>{text}</button>)
}

No entanto, você pode usar uniões para permitir que um único manipulador seja reutilizado por vários componentes:

/ ** Isso permitirá que você use esse manipulador de eventos, tanto em âncoras quanto em elementos de botão * /
função  eventHandler ( evento : React . MouseEvent < HTMLAnchorElement |  HTMLButtonElement > )  {
    console . log ( "TESTE!" )
}

Definição de tipo integrada

Finalmente, para a última dica, eu queria mencionar os arquivos index.d.ts e global.d.ts . Ambos estão instalados quando você adiciona React ao seu projeto (se você usou o npm, você os encontrará na pasta npm_modules / @ types.

Esses arquivos contêm definições de tipo e interface usadas pelo React; portanto, se você precisar entender os props de um tipo específico, poderá simplesmente abrir esses arquivos e revisar seu conteúdo.

Por exemplo:

Lá, você pode ver uma pequena seção do arquivo index.d.ts, mostrando as diferentes assinaturas da createElement função.

Conclusão

Há muito mais que você pode obter usando o TypeScript como parte de sua cadeia de ferramentas React. Portanto, se você viu pelo menos um trecho de que gostou aqui, compartilhe este artigo.

De qualquer forma, espero que você tenha extraído algo deste artigo e sinta-se à vontade para deixar outras dicas ou truques que você aprendeu ao longo dos anos usando o TypeScript para seus projetos React!

Vejo vocês no próximo artigo!

Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like