Definindo o type certo para seu array filter

matheusmoura17

Matheus Moura

Posted on March 15, 2024

Definindo o type certo para seu array filter

Um problema comum que muitos desenvolvedores enfrentam ao utilizar Array.filter em Typescript, é a utilização de runtime ao invés de type em operações de array.filter com checagem de nulos.

Exemplo:

// Types
interface IFruitModel {
    name: string
    icon: string
}

// Models
const banana: IFruitModel = {
    name: "Banana",
    icon: "🍌"
}

// Data manipulation
const fruits = [banana, null, banana]

const fruitsIcons = fruits
    .filter(fruit => !!fruit)
    .map(fruit => fruit.icon)

console.log(fruitsIcons)
Enter fullscreen mode Exit fullscreen mode

O código acima irá gerar o erro 'fruit' is possibly 'null'.(18047). Isso acontece pois durante o filter as frutas nulas são removidas, mas o type recebido no map é IFruitModel | null.

Durante o map, temos certeza que não existem frutas nulas, nosso código está correto e temos um erro de tipagem que não faz sentido.

Vou listar abaixo a forma incorreta, e a forma certa de resolver este problema.

🚨 Exemplo do que não fazer

const fruitsIcons = fruits
    .filter(fruit => !!fruit)
    .map(fruit => fruit as IFruitModel)
    .map(fruit => fruit.icon)
Enter fullscreen mode Exit fullscreen mode

Neste caso, é utilizado runtime para corrigir um problema de tipagem, comprometendo a performance do sistema em prol de um simples problema que poderia ser evitado, o código transpilado para JavaScript ficaria assim:

const fruitsIcons = fruits
    .filter(fruit => !!fruit)
    .map(fruit => fruit)
    .map(fruit => fruit.icon)
Enter fullscreen mode Exit fullscreen mode

Perceba que o primeiro map não faz nada a não ser retornar a própria fruta, é um desperdício de processamento.

✅ Exemplo do que fazer
A forma mais adequada de se resolver este problema, é definindo o predicado.

Em um array.filter(predicate: () => boolean), o predicado é a função que é passada por parâmetro e sempre retorna um booleano.

Podemos modificar este predicado para injetar um valor de saída no array.filter da seguinte forma:

const fruitsIcons = fruits
    .filter((fruit) : fruit is IFruitModel => !!fruit)
    .map(fruit => fruit.icon)
Enter fullscreen mode Exit fullscreen mode

Deste modo a fruta que entra no filter ainda é IFruitModel | undefined, mas o retorno será um array de IFruitModel. Viabilizando o map que vem em seguida.

O código equivalente em javascript seria:

const fruitsIcons = fruits
    .filter(fruit => !!fruit)
    .map(fruit => fruit.icon)
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
matheusmoura17
Matheus Moura

Posted on March 15, 2024

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

Sign up to receive the latest update from our blog.

Related