Dia 8 — Arrays
Criar, ler e alterar um array
Criar, ler e alterar um array
Array é a lista. No React Native é a estrutura que carrega quase tudo que a
tela mostra: as tarefas, os itens de um carrinho, as mensagens de um chat. Tudo
que vem de API chega como array.
const tarefas = ['estudar', 'treinar']; console.log(tarefas[0]); // 'estudar'
Cada posição é um índice, e começa em zero. O índice do segundo item é 1.
O length é o número de itens:
console.log(tarefas.length); // 2
O length não é só leitura: atribuir a ele corta o array. tarefas.length = 1
deixa só o primeiro item, e é a forma mais curta de esvaziar.
O exemplo abre com a lista de dois itens e imprime
indice 0: estudar | indice 1: treinar | length: 2. Os três dados da página
saem na mesma linha: o índice começa em zero, o segundo item é 1, e o
length é 2 — um a mais que o maior índice.
O corte pelo length aparece mais adiante, e a saída é length = 1 corta: [ 1 ].
Três itens viraram um, e o que sobrou foi o primeiro. Não há método para isso
porque o length é a propriedade que o array expõe para escrita — é o único
método de array que muda o array sem devolver nada.
Criar array de três jeitos
const a = [1, 2, 3]; // literal, o mais comum const b = new Array(3); // tres posicoes vazias const c = Array.of(1, 2, 3); // igual ao literal
O literal [] é o que se escreve. new Array(3) cria três posições vazias —
não o número 3 dentro, e é essa diferença que morde quem usa new Array por
engano. O array vazio é [], e Array.isArray([]) confirma que o valor é
array:
console.log(Array.isArray([]), Array.isArray('Ana'));
Para transformar outra coisa em array: Array.from('Ana') quebra a string em
letras, Array.from({ length: 3 }, (_, i) => i * 2) cria [0, 2, 4], e
[].concat(outro) junta dois arrays num terceiro.
As três conversões do exemplo estão em três linhas: `Array.from('Ana'): [ 'A',
'n', 'a' ], Array.from com funcao: [ 0, 2, 4 ] e concat: [ 1, 2, 3 ]`. A
segunda é a que mais aparece em app: o primeiro argumento { length: 3 } diz
quantos itens criar, e a função diz o valor de cada um — i * 2 produziu 0, 2
e 4. É a mesma forma do Array.from usado para gerar uma lista de teste sem
escrever os três valores à mão.
O concat começa com [] na frente, e o detalhe é que o array vazio é o
primeiro argumento: é ele que existe para receber o resultado. `[].concat([1, 2],
[3])` achata os dois arrays num só. Com um array no meio e outro depois, o
resultado seria [1, 2, 3, [4]] — o último não é achatado.
Acrescentar e remover
| Método | O que faz | Devolve |
|---|---|---|
push(item) | acrescenta no fim | novo length |
pop() | remove do fim | o item removido |
unshift(item) | acrescenta no começo | novo length |
shift() | remove do começo | o item removido |
push e pop mexem na ponta direita; unshift e shift na ponta esquerda.
unshift e shift são mais caros quando a lista é grande, porque todos os itens
mudam de posição.
O detalhe que gera bug: **pop devolve o item removido, push devolve o
tamanho.** Nenhum dos dois devolve a lista. Quem escreve `const lista =
tarefas.pop()` fica com o item, não com a lista.
O exemplo vai método por método e imprime o que cada um devolve e como a
lista ficou depois, que é a forma de não confundir os dois papéis.
push devolve o length: 4 | lista: [ 1, 2, 3, 4 ] — devolveu 4 e a lista
cresceu. pop devolve o item removido: 4 | lista: [ 1, 2, 3 ] — devolveu o 4
que saiu. unshift devolve o length: 4 | lista: [ 0, 1, 2, 3 ] e
shift devolve o item removido: 0 | lista: [ 1, 2, 3 ].
Repare que a lista volta a [ 1, 2, 3 ] depois das quatro operações, e foi
exatamente onde começou. Cada linha mostra o par completo: **o que o método
devolve e o que a lista ficou**. O nome da propriedade length e o nome
item removido nas duas primeiras linhas dizem qual dos dois é cada caso.
splice é o método que muda no meio: recebe posição, quantos remover e o que
colocar no lugar, e devolve os itens removidos.
const lista = [1, 2, 3, 4]; const removidos = lista.splice(1, 2, 'X'); // removidos = [2, 3], lista = [1, 'X', 4]
A linha do exemplo é splice devolve: [ 2, 3 ] | sobra: [ 1, 'X', 4 ], e os
dois lados batem com o código do bloco acima. A posição 1 é o 2, a
quantidade 2 tira o 2 e o 3, e o 'X' entra no lugar deles. Por isso o
splice é o método mais útil dos dois: o slice não coloca nada no lugar.
slice é o par não destrutivo de splice: devolve um array novo sem mexer no
original, e por isso const lista = tarefas.slice() é a forma de copiar uma
lista.
O exemplo mostra os dois na mesma linha, e a segunda parte é a prova do
parágrafo: slice: [ 2, 3 ] | o original continua: [ 1, 'X', 4 ]. O slice
devolveu [ 2, 3 ] — os mesmos itens que o splice tinha removido — e a lista
continua com o splice já aplicado. O slice pegou do resultado anterior
porque o splice tinha mudado a lista antes.
at e índice negativo
at aceita índice negativo e conta de trás — o último item é -1:
console.log(['a', 'b', 'c'].at(-1)); // 'c'
Com o colchete, [-1] não funciona: é a chave '−1' de um objeto, e devolve
undefined.
O exemplo põe os dois lado a lado, e é o par que fixa o método:
['a', 'b', 'c'].at(-1): c | o ultimo: c e, na linha seguinte,
colchete com -1 nao funciona: undefined. O mesmo índice negativo produziu uma
letra pelo at e nada pelo colchete.
O undefined não é esquisitice: o array tem as propriedades '0', '1', '2'
e length. O colchete procura a propriedade '−1', que não existe entre elas.
O at faz conta a partir do fim, e é por isso que ele aceita o sinal.
O at também aceita índice positivo, então at(1) e [1] dão o mesmo item. A
diferença é só o negativo.
Copiar array de verdade
Copiar com const copia = tarefas não copia: as duas variáveis apontam para
o mesmo array, e mexer em uma mexe na outra. A cópia de verdade usa spread ou
slice, e cria um array novo:
const copia = [...tarefas]; copia.push('dormir'); console.log(tarefas.length); // 2: o original não mudou
Para comparar dois arrays, === compara as referências e sempre dá false para
listas diferentes com mesmo conteúdo. O que compara conteúdo é
JSON.stringify(a) === JSON.stringify(b), ou o método includes quando o que se
pergunta é se um item está dentro.
A cópia do exemplo é uma linha só, e os três dados dela são a prova:
`original: [ 'estudar', 'treinar' ] | copia: [ 'estudar', 'treinar', 'dormir' ] |
mesma referencia: false. O push('dormir')` foi aplicado na cópia, o original
continua com dois itens, e o false do === diz que são dois arrays
diferentes. Com const copia = original, essa terceira parte sairia true e as
duas listas mudariam juntas.
As três comparações do fim do exemplo são a resposta completa para a pergunta
"esses dois arrays são iguais". '===' em arrays diferentes: false é o que a
comparação por referência devolve para [1, 2] contra outro [1, 2].
JSON.stringify compara conteudo: true é o mesmo par comparado pelo conteúdo, e
includes pergunta se o item esta dentro: true é outra pergunta — não é sobre
igualdade da lista, é sobre pertencer.
A última linha é a forma real do dado que vem de fora:
da api: estudar, treinar. A resposta da API é um objeto com tarefas sendo um
array de objetos, e a linha extrai os dois textos com map e join. É o mesmo
caminho que o map da próxima aula percorre em detalhe.
Exemplo
// Criar, ler e alterar array. O exemplo mostra os tres jeitos de criar, // os metodos das pontas e a diferenca entre copiar e apontar. const tarefas = ['estudar', 'treinar']; console.log('literal:', tarefas); console.log('indice 0:', tarefas[0], '| indice 1:', tarefas[1], '| length:', tarefas.length); /* criar de tres jeitos */ console.log('literal:', [1, 2, 3]); console.log('new Array(3) sao tres posicoes vazias:', new Array(3).length, JSON.stringify(new Array(3))); console.log('Array.of:', Array.of(1, 2, 3)); console.log('array vazio:', [], '| Array.isArray([]):', Array.isArray([]), '| de uma string:', Array.isArray('Ana')); /* transformar outra coisa em array */ console.log("Array.from('Ana'):", Array.from('Ana')); console.log('Array.from com funcao:', Array.from({ length: 3 }, (_, i) => i * 2)); console.log('concat:', [].concat([1, 2], [3])); /* acrescentar e remover nas pontas */ const lista = [1, 2, 3]; console.log('push devolve o length:', lista.push(4), '| lista:', lista); console.log('pop devolve o item removido:', lista.pop(), '| lista:', lista); console.log('unshift devolve o length:', lista.unshift(0), '| lista:', lista); console.log('shift devolve o item removido:', lista.shift(), '| lista:', lista); /* splice: muda no meio e devolve os removidos */ const meio = [1, 2, 3, 4]; const removidos = meio.splice(1, 2, 'X'); console.log('splice devolve:', removidos, '| sobra:', meio); /* slice: o par nao destrutivo de splice */ console.log('slice:', [1, 2, 3, 4].slice(1, 3), '| o original continua:', meio); /* length apaga por atribuicao */ const cortavel = [1, 2, 3]; cortavel.length = 1; console.log('length = 1 corta:', cortavel); /* at e indice negativo */ console.log("['a', 'b', 'c'].at(-1):", ['a', 'b', 'c'].at(-1), '| o ultimo:', ['a', 'b', 'c'].at(-1)); console.log('colchete com -1 nao funciona:', ['a', 'b', 'c'][-1]); /* copiar de verdade: com spread ou slice */ const original = ['estudar', 'treinar']; const copia = [...original]; copia.push('dormir'); console.log('original:', original, '| copia:', copia, '| mesma referencia:', original === copia); /* comparar conteudo, nao referencia */ console.log("'===' em arrays diferentes:", [1, 2] === [1, 2]); console.log('JSON.stringify compara conteudo:', JSON.stringify([1, 2]) === JSON.stringify([1, 2])); console.log('includes pergunta se o item esta dentro:', [1, 2, 3].includes(2)); /* o array que vem de API: a forma real */ const resposta = { tarefas: [{ id: 1, texto: 'estudar' }, { id: 2, texto: 'treinar' }] }; console.log('da api:', resposta.tarefas.map((t) => t.texto).join(', '));
Saída real
literal: [ 'estudar', 'treinar' ]
indice 0: estudar | indice 1: treinar | length: 2
literal: [ 1, 2, 3 ]
new Array(3) sao tres posicoes vazias: 3 [null,null,null]
Array.of: [ 1, 2, 3 ]
array vazio: [] | Array.isArray([]): true | de uma string: false
Array.from('Ana'): [ 'A', 'n', 'a' ]
Array.from com funcao: [ 0, 2, 4 ]
concat: [ 1, 2, 3 ]
push devolve o length: 4 | lista: [ 1, 2, 3, 4 ]
pop devolve o item removido: 4 | lista: [ 1, 2, 3 ]
unshift devolve o length: 4 | lista: [ 0, 1, 2, 3 ]
shift devolve o item removido: 0 | lista: [ 1, 2, 3 ]
splice devolve: [ 2, 3 ] | sobra: [ 1, 'X', 4 ]
slice: [ 2, 3 ] | o original continua: [ 1, 'X', 4 ]
length = 1 corta: [ 1 ]
['a', 'b', 'c'].at(-1): c | o ultimo: c
colchete com -1 nao funciona: undefined
original: [ 'estudar', 'treinar' ] | copia: [ 'estudar', 'treinar', 'dormir' ] | mesma referencia: false
'===' em arrays diferentes: false
JSON.stringify compara conteudo: true
includes pergunta se o item esta dentro: true
da api: estudar, treinar
Transformar: map, filter e reduce
Transformar: map, filter e reduce
Os três métodos que mais aparecem em React Native são map, filter e
reduce. Todos os três recebem uma função e devolvem um array novo — e é
isso que importa: nenhum dos três altera o array original. É o que permite que a
tela receba um valor novo e se redesenhe, sem estado se corromper.
A lista do exemplo é a base de tudo: três tarefas com id, texto e feita, e
as três primeiras linhas do console já mostram a propriedade do parágrafo.
original: [ 'estudar', 'treinar', 'dormir' ] é a lista intacta;
map: [ 'ANA', 'BRUNO' ] devolveu um array novo do mesmo tamanho; e
map nao altera o original: [ 'ana', 'bruno' ] prova que o array de entrada
continua minúsculo depois das transformações. É o mesmo array nas três linhas, e
só uma delas mudou.
| Método | Devolve | A função recebe | A função devolve |
|---|---|---|---|
map | array do mesmo tamanho | item | o item transformado |
filter | só os itens que passaram | item | true ou false |
reduce | um valor acumulado: é o que reduzir a lista faz | item, acumulador | o novo acumulador |
map: transformar item por item
map chama a função uma vez para cada item e monta um array com os retornos. O
tamanho da saída é o tamanho da entrada:
const nomes = ['ana', 'bruno']; console.log(nomes.map((nome) => nome.toUpperCase())); // ['ANA', 'BRUNO']
O segundo argumento é o índice, e o terceiro é o array inteiro. map é o que
desenha lista de componente: um View para cada item, com o item no meio.
A linha do exemplo que usa o índice é map com indice: [ '0: ana', '1: bruno' ].
A função recebeu (nome, indice) e montou o texto com os dois — e é assim que o
map numera uma linha de lista ou monta o texto de um botão que precisa saber
qual é o seu.
O erro clássico de map é o elemento sem chave, key:
{tarefas.map((tarefa) => <Item key={tarefa.id} tarefa={tarefa} />)}
Sem key, o React Native não sabe qual item da tela corresponde a qual item da
lista e a atualização sai errada. A chave é o id, nunca o índice.
filter: ficar com o que interessa
filter mantém os itens para os quais a função devolveu true:
const tarefas = [ { id: 1, texto: 'estudar', feita: false }, { id: 2, texto: 'treinar', feita: true }, ]; console.log(tarefas.filter((t) => !t.feita).length); // 1
filter não altera o array original e devolve um array com tamanho **menor ou
igual**. Ele é truthy e falsy: devolver o próprio objeto também passa, mas
devolver true ou false é mais legível.
O exemplo filtra as três tarefas e imprime
filter: [ 'estudar', 'dormir' ] | tamanho: 2. Entrada com três itens, saída
com dois — e são exatamente os dois com feita: false, porque a condição foi
!t.feita. A linha seguinte usa a condição invertida e sai
filter com truthy: [ 'treinar' ]: uma linha só, porque t.feita devolveu o
próprio booleano do objeto.
As duas linhas mostram o comportamento truthy e falsy na prática. A primeira
pede !t.feita, que é true ou false explícito; a segunda pede t.feita, que
já é o booleano do objeto. O filter aceitaria qualquer um dos dois — e também
aceitaria o objeto inteiro, que é truthy e por isso passa.
reduce: um valor só no fim
reduce percorre o array acumulando. O segundo argumento é o **valor inicial do
acumulador** e é obrigatório na forma que funciona sem valor inicial:
const numeros = [1, 2, 3]; console.log(numeros.reduce((acc, n) => acc + n, 0)); // 6
Com objeto no acumulador, reduce vira a forma de montar o agrupamento que a
tela precisa:
const porFeita = tarefas.reduce((acc, t) => { acc[t.feita ? 'prontas' : 'pendentes'] += 1; return acc; }, { prontas: 0, pendentes: 0 });
O detalhe que trava quem está começando: reduce não devolve nada se o
acumulador não voltar na função. A última linha do callback tem que ser return.
Sem ela, o reduce não acusa erro nenhum — devolve undefined em silêncio, e o
erro só aparece mais adiante, quando esse valor entra numa conta e vira NaN. É
o defeito mais silencioso desta lista, porque o código roda inteiro sem reclamar.
O exemplo tem três reduce que dão conta, e um quarto que dá o defeito.
reduce soma: 6 e reduce conta: 3 são as duas formas simples — a primeira
soma os números, a segunda só conta as voltas. O terceiro usa o acumulador
objeto e sai reduce com objeto no acumulador: { prontas: 1, pendentes: 2 }:
das três tarefas, uma está pronta e duas estão pendentes.
O quarto é o caso do parágrafo, e sai em duas linhas:
reduce sem return: undefined | typeof: undefined e
o undefined aparece depois na conta: NaN. O callback escrevia acc + n sem
devolver, então o acumulador sumiu logo na primeira volta — o reduce não
acusou erro, e o resultado é undefined. A linha seguinte mostra onde o estrago
aparece de verdade: undefined + 1 é NaN, longe do reduce que causou.
Os de buscar e os de testar
finddevolve o primeiro item que passa no teste, ouundefined.findIndexdevolve a posição do primeiro, ou-1.somedevolvetruese algum passa.everydevolvetruese todos passam.
tarefas.find((t) => t.feita).id; // 2 tarefas.findIndex((t) => t.feita); // 1 tarefas.some((t) => t.feita); // true tarefas.every((t) => t.texto); // true
As seis linhas do exemplo cobrem os quatro métodos e dois casos de borda.
find: 2 devolveu o id da primeira tarefa feita, e findIndex: 1 devolveu a
posição dela — mesmo item, dois números diferentes, porque um pergunta "qual" e o
outro pergunta "onde". some: true | every: true confirma os dois testes: pelo
menos uma está feita, e todas têm texto.
A linha find sem resultado: undefined é o find sem id atrás — quando não
acha nada, devolve o próprio undefined, e escrever .id nessa linha daria
TypeError. Por isso o find no app costuma vir com guarda: `const achado =
tarefas.find(...); if (achado) { ... }`.
O includes aparece na linha seguinte com os dois lados:
includes: false | no array de texto: true. A primeira é tarefas.includes({})
comparando contra um objeto novo — false, porque o includes compara
referência, e um objeto novo não é nenhum dos da lista. A segunda é o caso de
uso real, ['a', 'b'].includes('b').
includes responde se um valor exato está na lista; flatMap transforma e
achata um nível; sort e reverse ordenam e invertem no lugar — são os dois
únicos desta lista que alteram o array original. Para iterar array sem
alterar o original, quem resolve é slice: slice devolve um array novo com o
pedaço pedido, e o array de origem fica como estava. É também o slice que
flat e splice não substituem — cortar e filtrar são operações diferentes.
O flatMap e o flat do exemplo estão em duas linhas: flatMap: [ 1, 10, 2, 20 ]
e flat: [ 1, 2, 3, [ 4 ] ]. O primeiro transforma cada número em um par
[n, n * 10] e achata o resultado, então dois itens viram quatro. O segundo
achata dois níveis e o que sobrou aninhado continua aninhado — [ 4 ] dentro do
array é o resto que o flat(2) não alcançou.
E o reverse é reverse: [ 3, 2, 1 ], com o original intacto logo abaixo:
o original nao foi mexido: [ 10, 9, 1 ]. O exemplo espalha o array com
[...numeros] antes de cada sort, justamente porque o sort altera no lugar.
sort sem argumento compara como texto, e é por isso que [10, 9, 1].sort() dá
[1, 10, 9]: '10' vem antes de '9' porque '1' < '9'. Para número, o
argumento é a comparação:
console.log([10, 9, 1].sort((a, b) => a - b)); // [1, 9, 10]
As duas linhas do exemplo são sort sem argumento (como texto): [ 1, 10, 9 ] e
sort com comparacao: [ 1, 9, 10 ]. Mesmo array, mesmo sort, duas ordens: a
primeira é a comparação caractere a caractere de '10', '9', '1'; a
segunda é a subtração a - b, que devolve número em vez de booleano. O a - b
é o que diz ao sort qual item vem antes.
A última linha do exemplo junta os três em uma cadeia só:
encadeado: ESTUDAR, DORMIR. Filtrou as pendentes, transformou os textos em
maiúsculas e juntou com join. É a forma que o app usa para montar a linha de
resumo, e cada método da cadeia devolve o array que o próximo espera.
Exemplo
// map, filter e reduce: os tres que transformam lista, e os que so // procuram. Todos devolvem array novo e nao mexem no original. const tarefas = [ { id: 1, texto: 'estudar', feita: false }, { id: 2, texto: 'treinar', feita: true }, { id: 3, texto: 'dormir', feita: false }, ]; console.log('original:', tarefas.map((t) => t.texto)); /* map: transforma item por item, saida do mesmo tamanho */ const nomes = ['ana', 'bruno']; console.log('map:', nomes.map((nome) => nome.toUpperCase())); console.log('map com indice:', nomes.map((nome, indice) => `${indice}: ${nome}`)); console.log('map nao altera o original:', nomes); /* filter: fica com o que passou no teste */ const pendentes = tarefas.filter((t) => !t.feita); console.log('filter:', pendentes.map((t) => t.texto), '| tamanho:', pendentes.length); console.log('filter com truthy:', tarefas.filter((t) => t.feita).map((t) => t.texto)); /* reduce: um valor so no fim */ console.log('reduce soma:', [1, 2, 3].reduce((acc, n) => acc + n, 0)); console.log('reduce conta:', tarefas.reduce((acc) => acc + 1, 0)); console.log('reduce com objeto no acumulador:', tarefas.reduce((acc, t) => { acc[t.feita ? 'prontas' : 'pendentes'] += 1; return acc; // sem este return o acumulador some }, { prontas: 0, pendentes: 0 })); // sem o return, o acumulador some no meio das voltas /* o reduce sem return no callback nao acusa erro: devolve undefined */ const semRetorno = [1, 2, 3].reduce((acc, n) => { acc + n; }, 0); console.log('reduce sem return:', semRetorno, '| typeof:', typeof semRetorno); console.log('o undefined aparece depois na conta:', semRetorno + 1); /* os que procuram e os que testam */ console.log('find:', tarefas.find((t) => t.feita).id); console.log('findIndex:', tarefas.findIndex((t) => t.feita)); console.log('find sem resultado:', tarefas.find((t) => t.texto === 'correr')); console.log('some:', tarefas.some((t) => t.feita), '| every:', tarefas.every((t) => t.texto)); console.log('includes:', tarefas.includes({}), '| no array de texto:', ['a', 'b'].includes('b')); /* flatMap transforma e achata um nivel */ console.log('flatMap:', [1, 2].flatMap((n) => [n, n * 10])); console.log('flat:', [1, [2, [3, [4]]]].flat(2)); /* sort e reverse sao os que alteram no lugar */ const numeros = [10, 9, 1]; console.log('sort sem argumento (como texto):', [...numeros].sort()); console.log('sort com comparacao:', [...numeros].sort((a, b) => a - b)); console.log('reverse:', [1, 2, 3].reverse()); console.log('o original nao foi mexido:', numeros); /* o erro do map em quem nao e array */ try { 'Ana'.map((letra) => letra); } catch (erro) { console.log('map em string:', erro.constructor.name, '-', erro.message); } /* encadeamento: filtrar, transformar, contar */ console.log('encadeado:', tarefas .filter((t) => !t.feita) .map((t) => t.texto.toUpperCase()) .join(', '));
Saída real
original: [ 'estudar', 'treinar', 'dormir' ]
map: [ 'ANA', 'BRUNO' ]
map com indice: [ '0: ana', '1: bruno' ]
map nao altera o original: [ 'ana', 'bruno' ]
filter: [ 'estudar', 'dormir' ] | tamanho: 2
filter com truthy: [ 'treinar' ]
reduce soma: 6
reduce conta: 3
reduce com objeto no acumulador: { prontas: 1, pendentes: 2 }
reduce sem return: undefined | typeof: undefined
o undefined aparece depois na conta: NaN
find: 2
findIndex: 1
find sem resultado: undefined
some: true | every: true
includes: false | no array de texto: true
flatMap: [ 1, 10, 2, 20 ]
flat: [ 1, 2, 3, [ 4 ] ]
sort sem argumento (como texto): [ 1, 10, 9 ]
sort com comparacao: [ 1, 9, 10 ]
reverse: [ 3, 2, 1 ]
o original nao foi mexido: [ 10, 9, 1 ]
map em string: TypeError - "Ana".map is not a function
encadeado: ESTUDAR, DORMIR