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

Você não deveria usar `as DataType` no TypeScript

Introdução

O TypeScript, desde sua criação, tem como objetivo fornecer tipagem estática para JavaScript, ajudando os desenvolvedores a escrever códigos mais seguros e confiáveis. O operador as foi introduzido para permitir asserções de tipo, um recurso poderoso, mas com grande potencial de uso indevido. Neste artigo, exploraremos a criação e o propósito de as, erros comuns associados ao seu uso e como alternativas como o zod podem oferecer uma solução mais robusta.

A Criação de as no TypeScript

A palavra-chave as no TypeScript é usada para informar ao compilador que um valor deve ser tratado como um tipo específico, independentemente de sua inferência original. É útil em situações em que o desenvolvedor tem certeza sobre o tipo correto de uma variável, mas o compilador não consegue inferi-lo.

Por exemplo:

const value: unknown = getValueFromSomewhere();
const valueAsString = value as string; // Eu sei que `value` é uma string!

Nesse caso, as informa ao compilador que value é uma string, mesmo que tenha sido inferido inicialmente como unknown.

Erro Comum: Usando as com Fetch e JSON.parse

Embora as seja útil, pode ser facilmente usado de maneira inadequada, especialmente em cenários onde os dados vêm de fontes externas, como APIs.

Com Fetch

Ao usar fetch para obter dados de uma API, é tentador usar as para dizer ao TypeScript que tipo de dados esperar:

async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json() as MyDataType; // Cuidado!
    return data;
}

Aqui, estamos dizendo ao TypeScript: "Confie em mim, data é do tipo MyDataType." O problema é que fetch não garante o formato dos dados retornados. Isso pode levar a erros de tempo de execução que o TypeScript não pode prever ou evitar.

Com JSON.parse

O mesmo problema surge ao usar JSON.parse():

const jsonString = '{"name": "John", "age": 30}';
const parsedData = JSON.parse(jsonString) as MyDataType; // Arriscado!

Embora jsonString pareça conter um objeto do tipo MyDataType, não há garantias. Se a estrutura do JSON mudar ou estiver incorreta, o código falhará silenciosamente, levando a bugs difíceis de rastrear.

A Solução: Usando zod para Validação Segura de Tipos

Para evitar esses problemas, é melhor validar os dados externos antes de confiá-los. zod é uma biblioteca de validação de esquemas para TypeScript que pode ser usada para esse propósito.

Exemplo com fetch e zod

import { z } from 'zod';

const MyDataType = z.object({
    name: z.string(),
    age: z.number(),
});

async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    const json = await response.json();
    
    const result = MyDataType.safeParse(json);
    if (!result.success) {
        throw new Error('Dados inválidos');
    }

    return result.data; // Agora temos certeza de que `result.data` é do tipo correto!
}

Exemplo com JSON.parse e zod

const jsonString = '{"name": "John", "age": 30}';
const json = JSON.parse(jsonString);

const result = MyDataType.safeParse(json);
if (!result.success) {
    console.error(result.error);
} else {
    console.log('Dados válidos:', result.data);
}

Quando é Seguro Usar as

Embora tenhamos discutido os perigos do uso indevido de as, há um cenário específico onde eu pessoalmente acho aceitável usá-lo: Migração Gradual para TypeScript.

Migração Gradual para TypeScript

Durante a migração de uma grande base de código JavaScript para TypeScript, pode ser necessário usar as temporariamente para manter a compatibilidade enquanto você gradualmente adiciona tipos mais específicos e seguros ao seu código.

function processData(data: any) {
    const typedData = data as MyDataType; // Uso temporário
    // Lógica de processamento aqui
}

Este é um uso aceitável de as durante a transição, mas deve ser substituído por tipos ou validações mais precisas assim que possível.

Conclusão

Embora as possa ser uma ferramenta útil, seu uso indevido, especialmente ao lidar com dados dinâmicos ou externos, pode levar a erros silenciosos e difíceis de depurar em tempo de execução. Em vez disso, adotar ferramentas de validação como zod oferece uma abordagem mais robusta e segura, garantindo que os dados correspondam aos tipos esperados antes de confiá-los em seu código TypeScript. Pessoalmente, eu usaria as apenas no contexto de migração gradual para TypeScript, onde sua natureza temporária é compreendida, e planos estão em andamento para eliminá-lo.

Carregando publicação patrocinada...
3

Creio que outro uso aceitável é em testes automatizados. As vezes tu só precisa testar uma pequena parte de um objeto ou até mesmo só tipar ele para não quebrar os checks estáticos. E ter algo como {} as unknown as DataType para não quebrar os testes resolve.

0
2

Realmente, é bem perigoso ficar usando 'as' em contextos onde nao se tem plena certeza que o dado é realmente do tipo inferido. Eu tenho estuda onuso da biblioteca yup para realizar validações, o que me interessa nela é poder colocar mais restrições além de somente o tipo, por exemplo, no schema eu posso colocar uma propriedade como string de pelo menos n caracteres e o yup consegue fazer essa validação pra mim. Outra que aprendi a usar a um tempo é a fireorm (parecida com a typeorm mas para integração com o firebase), ela tem um funcionamento parecido só que através de decorators que colocamos nas propriedades de uma classe. Uma pergunta, o zod teria funções de validação como essas do yuo e fireorm?

2

O zod é muito útil mesmo, ainda mais lidando com types grandes e complexos.

Também é possível fazer validações simples criando type guards usando type predicates e typeof:

type MyData = {
  name: string;
  age: number;
};

// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
function isMyDataType(data: any): data is MyData {
  if (typeof data?.name !== "string") return false;
  if (typeof data?.age !== "number") return false;
  return true;
}

const json1 = '{ "name": "John", "age": 30 }';
const data1 = JSON.parse(json1);

if (isMyDataType(data1)) {
  console.log("`data1` is valid:", data1);
} else {
  console.log("`data1` is not valid");
}

const json2 = '{ "firstName": "John", "yearOfBirth": 1994 }';
const data2 = JSON.parse(json2);

if (isMyDataType(data2)) {
  console.log("`data2` is valid:", data2);
} else {
  console.log("`data2` is not valid");
}

// output:
// `data1` is valid: { name: 'John', age: 30 }
// `data2` is not valid

Depois de checar o type, sabe-se que é o type certo dentro do if:

Ou, por exemplo, fora do if com throw:

Também é possível incorporar o zod no type guard, algo como:

function isComplexDataType(data: any): data is MyComplexData {
  const result = complexSchema.safeParse(data);

  return result.success;
}
1

Acho que existem outras situações que podem ser interessantes. Por exemplo, em alguns casos você faz alguma verificação, mas o typescript não consegue entender.

(não verifiquei o exemplo abaixo, mas já passei por situações similares)

const other = (param: string) => {
 // ...
 return 123
}

const foo = (a: string | null) => {
  const b = a
  
  if (typeof b === "string") {
    // aqui poderia usar o as para garantir que é string
    return other(a)
  }
  
  return null
}

Outro caso seria o erro que vem do try catch

1

Experimenta isso:

const other = (parameter: string) => {
  // ...
  return 123
}

function isString(stringOrNull: string | null): stringOrNull is string {
  return typeof stringOrNull === 'string'
}

const foo = (a: string | null) => {
  const b = a

  if (isString(b)) {
    // aqui poderia usar o as para garantir que é string
    return other(b)
  }

  return null
}