Executando verificação de segurança...
8

Como usar Type Predicate do TypeScript no React?

Vamos imaginar um cenário em que você tem um componente chamado User que é responsável por renderizar um usuário.
Esse componente recebe um objeto chamado user que contém as informações do usuário, sendo que estas informações seguem esse tipo:

type User = {
  name: string;
  age: number;
  email?: string;
}

Com base na interface, a gente estabelece que o usuário tem nome, idade e email, mas o email não é obrigatório.
Agora falando da implementação do componente User, além de exibir as informações do usuário, ele também deve truncar o email caso este seja maior que 20 caracteres seguidos de três pontos (...), caso contrário, deve exibir o email completo. A gente pode fazer isso da seguinte forma:

type UserProps = {
  user: User;
};

function User({ user }: UserProps) {
  return (
    <div>
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      {user.email && <h3>{user.email.length > 20 ? `${user.email.substring(0, 20)}...` : user.email}</h3>}
    </div>
  );
}

Legal, esta implementação funciona perfeitamente e atende o que é preciso. Mas e se o email passa a ter um novo requisito e agora deva ser exibido somente se possuir o domínio @gmail.com, teríamos que alterar a implementação para:

function User({ user }: UserProps) {
  return (
    <div>
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      {user.email && user.email.includes('@gmail.com') && <h3>{user.email.length > 20 ? `${user.email.substring(0, 20)}...` : user.email}</h3>}
    </div>
  );
}

Como podemos ver, o componente está ficando cada vez mais complexo e menos legível com o tempo. A gente pode resolver isso utilizando de funções auxiliares, como por exemplo:

function User({ user }: UserProps) {
  function shouldShowEmail(email?: string): boolean {
    if (!email) return false;
    return email.includes('@gmail.com');
  }

  function truncateEmail(email: string): string {
    return email.length > 20 ? `${email.substring(0, 20)}...` : email;
  }

  return (
    <div>
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      {shouldShowEmail(user.email) && <h3>{truncateEmail(user.email)}</h3>}
    </div>
  );
}

E aí que entra nosso problema, a partir desta refatoração, o typescript passa a reclamar que a função truncateEmail espera receber um email que seja string porém estamos informando um valor que é string ou undefined. Mas espera, a gente não está validando
no trecho anterior se o email tem valor na função shouldShowEmail? Como a gente pode falar pro TypeScript que é certeza que naquele momento email será uma string? Podemos fazer isso:

function User({ user }: UserProps) {
  return (
    <div>
      ...
      {shouldShowEmail(user.email) && <h3>{truncateEmail(user.email as string)}</h3>}
    </div>
  );
}

Se você pensou nessa solução, você está certo e não tem problema nenhum em usar a keyword as para dizer imperativamente que o email é uma string. O desenvolvedor sabe mais do que o TypeScript e tudo bem você usar o as quando possível.
Porém, e se o trecho do truncate passar a usar mais funções que dependam deste valor ou que seja necessário manipular o email, teremos que passar o as todas as vezes? Sim! E como podemos resolver isso? Aí que aparece o Type Predicate e a keyword
is, vamos dar uma olhada:

function User({ user }: UserProps) {
  // Agora estamos falando para esta função que, se o valor retornar false
  // significa que email não é uma string e se retornar true, email é uma string
  function shouldShowEmail(email?: string): email is string {
    if (!email) return false;
    return email.includes('@gmail.com');
  }

  ...

  return (
    <div>
      ...
      {shouldShowEmail(user.email) && <h3>{truncateEmail(user.email)}</h3>}
    </div>
  );
}

Aha! E esta é a solução ideal, conseguimos falar para o typescript que a partir do momento que o shouldShowEmail retornar true todas as referências seguintes para o valor user.email serão string sem a necessidade de usar as todas as vezes.
Bem massa, né?

É isso, galera, este foi meu primeiro post, espero que tenha sido de ajuda pra vocês, fiquem a vontade para dar feedback ou complementarem com mais exemplos. Valeu!

Carregando publicação patrocinada...
1
1
1
1
1
1