Como implementar Type guards no seu código

samuelmpinho

Samuel Monteiro

Posted on May 20, 2021

Como implementar Type guards no seu código

Imagine que você tem a seguinte situação:

Você tem uma aplicação que busca por músicas no Spotify e nessa aplicação você mostra o email do usuário logado e a música que ele está escutando naquele momento.

Você então cria 2 funções para acessar a API do Spotify

  • getUser para chamar a API que irá buscar os dados do usuário
  • getSong que irá retornar uma lista de músicas do Spotify

Então você teria o seguinte código:

type APIResponse<T> = {
  data: T;
  error: Error;
}

type User = {
  email: string;
}

type Song = { id: string; title: string };

const getUser = async (fetchUrl: string): APIResponse<User> => {
    const res = await fetch(fetchUrl);

    return res;
}

const getSong = async (fetchUrl: string): APIResponse<Song> => {
    const res = await fetch(fetchUrl);

    return res;
}
Enter fullscreen mode Exit fullscreen mode

Depois de implementar as 2 funções, você percebe que elas são muito parecidas. As 2 recebem uma url que é passada para dentro do método fetch. Esse método faz a chamada para a API e depois retorna o resultado.

E aí, você pode pensar

poxa, já que elas são tão parecidas, acho que vou criar uma função que reduz a quantidade de código repetido

const fetchApi = async (fetchUrl: string): APIResponse<User | Song> => {
    const res = await fetch(fetchUrl);

  return res;
}

const userResppnse = await fetchApi('https://apiUser...');
const songResponse = await fetchApi('https://apiSong...');
Enter fullscreen mode Exit fullscreen mode

Já parece que ficou melhor. Agora temos menos código duplicado e menor possibilidade de erro.

Mas você vai ter um problema de Typescript 😟

O tipo da variável userResponse está como APIResponse<User | Song> então se você tentar fazer isso:

const userResponse = await fetchApi('https://...');

console.log(userResponse.data.email);
Enter fullscreen mode Exit fullscreen mode

Você vai tomar o seguinte erro:

Property 'email' does not exist on type 'User | Songs[]'.
  Property 'email' does not exist on type 'Songs[]'
Enter fullscreen mode Exit fullscreen mode

O Typescript não consegue dizer se a propriedade data da variável userResponse é um User ou um Song e por isso ele previne que você chame a propriedade email que é exclusiva do User.

Isso garante que se por exemplo isso acontecer:

const userResponse = fetch('https://apiSong...');
Enter fullscreen mode Exit fullscreen mode

Você não vai ter problemas mais pra frente.

Type guards

Os type guards são uma forma de dizer para o Typescript qual tipo nós esperamos.

Podemos criar um type guard de usuário que garante o tipo User da seguinte forma:

const isUser = (data: User | Song): data is User => (data as User).email !== undefined;
Enter fullscreen mode Exit fullscreen mode

Estamos criando a função isUser que aceita o tipo data: User | Song e que retorna um data is User. A expressão is User diz que eu, como desenvolvedor, garanto que o retorno da minha função é do tipo User.

O que falta é a implementação dessa verificação. Já que o tipo Song não tem o campo email podemos verificar se a variável data possuí ele.

Se possuir, podemos dizer que data é do tipo User.

E com isso, podemos executar o seguinte código sem problemas de Typescript:

if(isUser(userResponse.data)) {
  console.log(userResponse.data.email)
};
Enter fullscreen mode Exit fullscreen mode

Assim, o Typescript não vai mostrar um erro porque você disse que a função isUser retorna User e nada mais.

Não tem possibilidade da variável data ser do tipo Song por exemplo.

E para finalizar, o type guard do tipo Song seria assim:

const isSong = (data: User | Song): data is Song => (data as Song).title !== undefined;

if(isSong(songResponse.data)) {
  console.log(songResponse.data.title);
}
Enter fullscreen mode Exit fullscreen mode

Através dos type guards você consegue ter maior controle sobre a tipagem do seu código. Tudo isso em tempo de runtime, o que é bem legal 🤘

Apesar de ser uma boa adição na sua base de código, o type guards é propenso a falhas já que quem define a função para validar o tipo é o próprio desenvolvedor.

Para resolver essas falhas e realmente trabalhar com um typchecking em runtime, sugiro dar uma lida nesse artigo do pessoal do Unsplash. O artigo destaca o porquê de type guards definidos pelo usuário não serem tão seguros e o que fazer nesses casos.


Galera, é isso por hoje. Fiz esse artigo porque acabei me deparando com algumas situações na minha aplicação que funcionariam melhor se tivesse um type guard implementado. E apesar de serem muito úteis, não vejo muita gente usando no dia a dia, então fica aqui minha contribuição. Espero que consigam aproveitar de alguma forma 😉

E se vocês sentiram falta de alguma explicação, ficaram com alguma dúvida ou só querem trocar uma ideia, podem me marcar ou mandar DM no twitter 🤙

💖 💪 🙅 🚩
samuelmpinho
Samuel Monteiro

Posted on May 20, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related