Agrupamento com Array.prototype.groupBy

Lucas Santos - Feb 16 '22 - - Dev Community

Foto de capa por Markus Spiske no Unsplash

Desde quando eu comecei a programar, sempre me peguei em situações onde eu precisava utilizar uma função simples, mas ao mesmo tempo inexistente nas linguagens que eu estava trabalhando.

Isso não foi diferente quando eu tive que trabalhar com JavaScript e precisei fazer uma simples ação de agrupamento, ou seja, eu tinha que separar meu objeto ou array em pequenos grupos de acordo com o tipo de item que eu tinha neles.

Felizmente, uma das salvações de todo dev quando precisa utilizar funções muito comuns é apelar para bibliotecas de utilidades, a mais famosa hoje é o LoDash, que coincidentemente possui um método chamado groupBy.

Mas a época de baixar bibliotecas externas para essas funções simples está acabando porque agora podemos ter o nosso próprio groupBy, só que nativo.

Agrupamento

As funções de agrupamento entram em uma classe de funções que eu gosto de chamar de incríveis e preguiçosas, porque essas funções são extremamente úteis para praticamente todo o tipo de coisa que a gente pode fazer em desenvolvimento, ao mesmo tempo que elas são rápidas e simples de implementar, mas elas são tão simples que, ao mesmo tempo, não vale a pena escrever algo do zero.

Por isso muitas pessoas recorrem a baixar uma biblioteca externa como o LoDash para poder ter o problema resolvido de forma simples e prática.

Eu nunca fui um grande fã de ter que baixar uma biblioteca e criar uma dependência de um código externo para uma função tão simples, especialmente se eu vou usar ela uma única vez no meu código. Então eu prefiro fazer essas funções na mão.

Existem infinitas formas de você realizar um agrupamento simples, e por agrupamento simples eu estou dizendo ter a capacidade de pegar uma série de itens dentro de um array e organizá-los em categorias, por exemplo, separar usuários dentro de um sistema por seu nível de acesso:

const usuarios = [
    { name: 'Lucas', role: 'admin' },
    { name: 'Ana', role: 'reader' },
    { name: 'Erick', role: 'reader' },
    { name: 'Beatriz', role: 'writer' },
    { name: 'Carla', role: 'admin' }
]
Enter fullscreen mode Exit fullscreen mode

A saída que eu quero é algo assim:

const groups = {
    admin: [
        {name: 'Lucas', role: 'admin'}, 
        {name: 'Carla', role: 'admin'}
    ],
    reader: [
        { name: 'Ana', role: 'reader' },
        { name: 'Erick', role: 'reader' },
    ],
    writer: [
        { name: 'Beatriz', role: 'writer' }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Então como a gente faz uma função desse tipo? A forma mais simples que eu consigo pensar é com um reduce:

function groupBy (array, key) {
    return array.reduce((acc, item) => {
        if (!acc[item[key]]) acc[item[key]] = []
        acc[item[key]].push(item)
        return acc
    }, {})
}
Enter fullscreen mode Exit fullscreen mode

Existe uma outra forma de agrupar se a gente simplificar um pouco mais o reduce para usar o spread operator :

function groupBy (array, key) {
    return array.reduce((acc, item) => ({
      ...acc,
      [item[key]]: [...(acc[item[key]] ?? []), item],
    }),
  {})
}
Enter fullscreen mode Exit fullscreen mode

Mas existem alguns artigos que comentam que talvez usar o spread para este caso possa ser uma má ideia, já que vamos ter um loop "escondido" que pode tornar a nossa função uma função de complexidade exponencial.

Então temos o lodash.groupBy que é quase a mesma implementação, porém com alguns tipos para compatibilidade e alguns controles de erros mais estritos. Função que, junto com outras como o intersect e o difference, são incríveis e preguiçosas.

A solução nativa

Recentemente o comitê do TC39, a organização que mantém e dirige o JavaScript, anunciou que uma de suas propostas, a que vai incluir o novo método groupBy dentro do Array.prototype, já chegou no estágio 3!

Se você não sabe como funciona o processo de publicação e evolução do JavaScript, esse vídeo vai te ajudar a entender tudo.

Isso significa que logo mais podemos ver uma implementação exatamente como esta aqui nos códigos de sistemas pelo mundo:

const usuarios = [
    { name: 'Lucas', role: 'admin' },
    { name: 'Ana', role: 'reader' },
    { name: 'Erick', role: 'reader' },
    { name: 'Beatriz', role: 'writer' },
    { name: 'Carla', role: 'admin' }
]

const grouped = usuarios.groupBy(({role}) => role)
Enter fullscreen mode Exit fullscreen mode

A ideia é que essa funcionalidade seja lançada como parte do ES2022 durante este ano e entre como parte integrante dos browsers daqui a um tempo.

Mas se você é que nem eu e quer testar essa funcionalidade o mais rápido possível, então você precisa de um shim de implementação ou então esperar para usar o babel no preset do stage-3 para poder codar da mesma forma que você já faz hoje!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .