O Junior Martins postou essa dúvida no nosso Discord, e eu respondi ele por lá, mas o Gabe sugeriu que a resposta virasse um post, então aqui está!
O is
é usado quando você quer criar seus próprios type guards: você cria uma função que retorna um booleano (esse tipo de função se chama "predicado"), e usa o is
para dizer que, se a função retornar true
, quer dizer que o valor passado via argumento é de um tipo específico.
Por exemplo: imagine que você vai fazer um request para uma API, para buscar um produto.
A resposta pode ser um objeto com os dados do produto ou null
, se o produto não for encontrado.
Poderíamos representar em tipos assim:
type Product = {
id: string
name: string
price: number
}
type ProductResponse = Product | null
Agora vamos fazer uma função que vai fazer esse request (fake, obviamente).. ignore a implementação, a ideia é só retornar um objeto do tipo ProductResponse
, ou seja: ou vai retornar um Product
, ou vai retornar null
:
async function getProduct (id: string): Promise<ProductResponse> {
const product = {
id,
name: 'TV',
price: 5000
}
const possibleResponse = [product, null]
const index = Math.floor(Math.random() * possibleResponse.length)
return possibleResponse[index]
}
Ao executar essa função, o retorno vai ser uma Promise
de ProductResponse
. Se você tentar acessar o valor de price
da resposta - já que a função pode retornar um produto - o TS vai mostrar o erro:
Object is possibly 'null'
A chamada seria algo assim:
getProduct('tv123').then(response => {
console.log(response.price)
})
Para garantir que o objeto não é nulo, precisamos usar um type guard, verificando o tipo do dado para garantir que ele não é nulo antes de acessar as propriedades do objeto:
getProduct('tv123').then(response => {
if (response === null) {
// aqui o TS sabe que `response` é null
return
}
// aqui o TS sabe que `response` é `Product`, pois `null`
// e `Product` são as únicas possíveis opções para `response`
console.log(response.price)
})
Usar um if
com uma comparação por algum tipo (testando se o dado é igual a null
, ou usando o typeof
, por exemplo), é um type guard interno do TypeScript. Mas pense o seguinte: e se você quiser criar seu próprio type guard? Possivelmente você tentaria fazer algo assim:
function isProduct (value: ProductResponse) {
return value !== null
}
E então testaria o valor:
getProduct('tv123').then(response => {
if (!isProduct(response)) {
return
}
console.log(response.price)
})
A surpresa acontece quando descobrimos que o TS agora não consegue inferir que response
só será null
se cair no if
, e dá o mesmo erro:
Object is possibly 'null'.
É aí que entra o is
!
Como a função isProduct
é apenas uma função que recebe um argumento qualquer, e retorna um valor qualquer, o TS não vai conseguir inferir que o retorno é um Product
, e que esse retorno está diretamente relacionado com o argumento passado para a função.
Para fazer o TS entender essa relação, usamos o is
: temos que dizer ao TS que, quando executarmos a função isProduct
passando o argumento response
, se a função retornar true
, quer dizer que o argumento É, de fato, um Product
.
Para dizer isso ao TS, só precisamos alterar o tipo de retorno da função isProduct
:
function isProduct (value: ProductResponse): value is Product {
return value !== null
}
Pronto! Agora o TS vai saber quando o response é null
e quando ele é um Product
em um escopo completamente diferente da função isProduct
!
getProduct('tv123').then(response => {
if (!isProduct(response)) {
// agora o TS sabe - novamente - que `response` é null, pois estamos
// verificando se `response` NÃO é um `Product`
return
}
// e aqui o TS sabe que `response` é `Product`, pois `null`
// e `Product` são as únicas possíveis opções para `response`
console.log(response.price)
})
Podemos também criar um type guard mais genérico, para testar se o valor é null, fazendo uma verificação positiva, ao invés de criar um if
negativo, que dificulta um pouco a leitura:
function isNull (value: unknown): value is null {
return value === null
}
E então só precisamos usar essa função de type guard:
getProduct('tv123').then(response => {
if (isNull(response)) {
// aqui dentro o TS sabe que `response` é null
return
}
// e aqui o TS sabe que `response` é `Product`, pois `null`
// e `Product` são as únicas possíveis opções para `response`
console.log(response.price)
})
E tudo funciona como esperado, inclusive o autocomplete das propriedades price
, name
e id
em response
;)
Você pode brincar com esse código direto do Playground do TypeScript!
Por hoje é só! Qualquer dúvida ou sugestão, deixe nos comentários :D
Sobre o autor
Você encontra minhas informações de contato e redes sociais rodando o seguinte comando no seu terminal:
npx fdaciuk
Top comments (4)
olha Fernando... essa foi a primeira vez que vi algo didático de Typescript e pra mim foi algo que ajudou a desvendar bastante coisa... porque o TS vem de encontro com linguagem tipada e facilita muito meu entendimento.
Obrigado por compartilhar e por favor, me adiciona se você tiver mais conteúdo de TS também para ficar na lista de leitura.
Forte abraço
Massa Alexandre, que bom que curtiu! Eu pretendo começar a compartilhar mais conteúdo de TS escrito sim :D
Li ontem e já me ajudou hoje. Valeu!
Que massa @brenonovelli :D