Como atualizar o elemento dentro da List com ImmutableJS?

129

Aqui está o que os documentos oficiais disseram

updateIn(keyPath: Array<any>, updater: (value: any) => any): List<T>
updateIn(keyPath: Array<any>, notSetValue: any, updater: (value: any) => any): List<T>
updateIn(keyPath: Iterable<any, any>, updater: (value: any) => any): List<T>
updateIn(keyPath: Iterable<any, any>, notSetValue: any, updater: (value: any) => any): List<T>

Não há como o desenvolvedor web normal (não o programador funcional) entender isso!

Eu tenho um caso bastante simples (para uma abordagem não funcional).

var arr = [];
arr.push({id: 1, name: "first", count: 2});
arr.push({id: 2, name: "second", count: 1});
arr.push({id: 3, name: "third", count: 2});
arr.push({id: 4, name: "fourth", count: 1});
var list = Immutable.List.of(arr);

Como posso atualizar listonde o elemento com nome terceiro tem sua contagem definida como 4 ?

Vitalii Korsakov
fonte
43
Eu não sei como é semelhante, mas a documentação é terrível facebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov
71
Voto positivo grave para a escavação na documentação. Se o objetivo dessa sintaxe é fazer com que eu me sinta idiota / inadequado, sucesso definido. Se o objetivo, no entanto, é a de transmitir obras como imutáveis, bem ...
Owen
9
Tenho que concordar, acho a documentação do immutable.js muito frustrante. A solução aceita para isso usa um método findIndex () que eu nem vejo nos documentos.
RichardForrester
3
Prefiro que eles dêem um exemplo para cada função, em vez dessas coisas.
RedGiant
4
Acordado. A documentação deve ter sido escrita por algum cientista em uma bolha, não por alguém que queira mostrar alguns exemplos simples. A documentação precisa de alguma documentação. Por exemplo, eu gosto da documentação de sublinhado, muito mais fácil de ver exemplos práticos.
ReduxDJ

Respostas:

119

O caso mais apropriado é usar os métodos findIndexe update.

list = list.update(
  list.findIndex(function(item) { 
    return item.get("name") === "third"; 
  }), function(item) {
    return item.set("count", 4);
  }
); 

PS Nem sempre é possível usar o Maps. Por exemplo, se os nomes não são exclusivos e eu quero atualizar todos os itens com os mesmos nomes.

Vitalii Korsakov
fonte
1
Se você precisar de nomes duplicados, use multimaps - ou mapas com tuplas como valores.
Bergi
5
Isso não atualizará inadvertidamente o último elemento da lista se não houver nenhum elemento com "três" como o nome? Nesse caso, findIndex () retornaria -1, que update () interpretaria como o índice do último elemento.
Sam Storie
1
Não, -1 não é o último elemento #
Vitalii Korsakov 25/11
9
@ Sam Storie está certo. Nos documentos: "o índice pode ser um número negativo, que retorna do final da lista. V.update (-1) atualiza o último item da lista". facebook.github.io/immutable-js/docs/#/List/update
Gabriel Ferraz
1
Não pude chamar item.set porque para mim o item não era do tipo Imutável, tive que mapear o item primeiro, chamar o conjunto e depois convertê-lo novamente em objeto. Portanto, neste exemplo, function (item) {return Map (item) .set ("count", 4) .toObject (); }
syclee 8/08/16
36

Com .setIn () você pode fazer o mesmo:

let obj = fromJS({
  elem: [
    {id: 1, name: "first", count: 2},
    {id: 2, name: "second", count: 1},
    {id: 3, name: "third", count: 2},
    {id: 4, name: "fourth", count: 1}
  ]
});

obj = obj.setIn(['elem', 3, 'count'], 4);

Se não soubermos o índice da entrada, queremos atualizar. É muito fácil encontrá-lo usando .findIndex () :

const indexOfListToUpdate = obj.get('elem').findIndex(listItem => {
  return listItem.get('name') === 'third';
});
obj = obj.setIn(['elem', indexOfListingToUpdate, 'count'], 4);

Espero que ajude!

Albert Olivé
fonte
1
você está perdendo por { }dentro fromJS( ), sem o aparelho não é JS válido.
Andy
1
Eu não chamaria o objeto retornado de 'arr', pois é um objeto. única arr.elem é um array
Nir O.
21
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

Explicação

A atualização de coleções Immutable.js sempre retorna novas versões dessas coleções, mantendo o original inalterado. Por isso, não podemos usar a list[2].count = 4sintaxe de mutação do JavaScript . Em vez disso, precisamos chamar métodos, como podemos fazer com as classes de coleção Java.

Vamos começar com um exemplo mais simples: apenas as contagens em uma lista.

var arr = [];
arr.push(2);
arr.push(1);
arr.push(2);
arr.push(1);
var counts = Immutable.List.of(arr);

Agora, se nós queria para atualizar o terceiro item, uma matriz JS simples pode parecer: counts[2] = 4. Como não podemos usar mutação e precisamos chamar um método, podemos usar: counts.set(2, 4)- isso significa definir o valor 4no índice 2.

Atualizações profundas

O exemplo que você deu aninhou os dados. Não podemos usar apenas set()na coleção inicial.

As coleções do Immutable.js têm uma família de métodos com nomes terminados em "In", que permitem fazer alterações mais profundas em um conjunto aninhado. Os métodos de atualização mais comuns têm um método "In" relacionado. Por exemplo, para setexiste setIn. Em vez de aceitar um índice ou uma chave como o primeiro argumento, esses métodos "In" aceitam um "caminho da chave". O caminho da chave é uma matriz de índices ou chaves que ilustra como chegar ao valor que você deseja atualizar.

No seu exemplo, você deseja atualizar o item na lista no índice 2 e, em seguida, o valor na chave "count" dentro desse item. Então, o caminho principal seria [2, "count"]. O segundo parâmetro para o setInmétodo funciona exatamente como seté o novo valor que queremos colocar lá, então:

list = list.setIn([2, "count"], 4)

Encontrando o caminho da chave certo

Indo um passo adiante, você realmente disse que queria atualizar o item em que o nome é "três", que é diferente do que apenas o terceiro item. Por exemplo, talvez sua lista não esteja classificada ou talvez o item chamado "dois" tenha sido removido anteriormente? Isso significa que primeiro precisamos ter certeza de que realmente sabemos o caminho da chave correto! Para isso, podemos usar o findIndex()método (que, a propósito, funciona quase exatamente como Array # findIndex ).

Depois de encontrarmos o índice na lista que possui o item que queremos atualizar, podemos fornecer o caminho da chave para o valor que desejamos atualizar:

var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

NB: SetvsUpdate

A pergunta original menciona os métodos de atualização em vez dos métodos definidos. Vou explicar o segundo argumento nessa função (chamada updater), já que é diferente de set(). Enquanto o segundo argumento para set()é o novo valor que queremos, o segundo argumento para update()é uma função que aceita o valor anterior e retorna o novo valor que queremos. Então, updateIn()é a variação "In" deupdate() que aceita um caminho de chave.

Digamos, por exemplo, que desejássemos uma variação do seu exemplo que não apenas definisse a contagem 4, mas, ao invés disso, aumentasse a contagem existente, poderíamos fornecer uma função que adiciona uma ao valor existente:

var index = list.findIndex(item => item.name === "three")
list = list.updateIn([index, "count"], value => value + 1)
Lee Byron
fonte
19

Aqui está o que os documentos oficiais disseram ... updateIn

Você não precisa updateIn, que é apenas para estruturas aninhadas. Você está procurando o updatemétodo , que possui uma assinatura e documentação muito mais simples:

Retorna uma nova lista com um valor atualizado no índice com o valor retornado de chamar o atualizador com o valor existente ou notSetValue se o índice não foi definido.

update(index: number, updater: (value: T) => T): List<T>
update(index: number, notSetValue: T, updater: (value: T) => T): List<T>

que, como sugerem os Map::updatedocumentos , é " equivalente a:list.set(index, updater(list.get(index, notSetValue))) ".

onde elemento com o nome "terceiro"

Não é assim que as listas funcionam. Você precisa conhecer o índice do elemento que deseja atualizar ou procurar por ele.

Como posso atualizar a lista em que o elemento com nome terceiro tem sua contagem definida como 4?

Isso deve servir:

list = list.update(2, function(v) {
    return {id: v.id, name: v.name, count: 4};
});
Bergi
fonte
14
Não, sua escolha da estrutura de dados não atende à lógica de negócios :-) Se você deseja acessar elementos pelo nome, deve usar um mapa em vez de uma lista.
Bergi
1
@bergi, realmente? Portanto, se eu tiver, digamos, uma lista de alunos em uma turma, e desejar realizar operações CRUD nesses dados, usar uma abordagem Mapsobre a Listé a que você sugeriria? Ou você diz apenas isso porque o OP disse "selecionar item pelo nome" e não "selecionar item pelo id"?
David Gilbertson
2
@DavidGilbertson: CRUD? Eu sugiro um banco de dados :-) Mas sim, um mapa de identificação-> aluno parece mais apropriado do que uma lista, a menos que você tenha um pedido e queira selecioná-lo pelo índice.
Bergi
1
@bergi Acho que concordamos em discordar, recebo muitos dados das APIs na forma de matrizes de coisas com IDs, onde preciso atualizar essas coisas por ID. Esta é uma matriz JS e, em minha opinião, deve ser uma lista JS imutável.
David Gilbertson
1
@ DavidGilbertson: Eu acho que essas APIs usam matrizes JSON para conjuntos - que são explicitamente desordenadas.
Bergi
12

Use .map ()

list = list.map(item => 
   item.get("name") === "third" ? item.set("count", 4) : item
);

var arr = [];
arr.push({id: 1, name: "first", count: 2});
arr.push({id: 2, name: "second", count: 1});
arr.push({id: 3, name: "third", count: 2});
arr.push({id: 4, name: "fourth", count: 1});
var list = Immutable.fromJS(arr);

var newList = list.map(function(item) {
    if(item.get("name") === "third") {
      return item.set("count", 4);
    } else {
      return item;
    }
});

console.log('newList', newList.toJS());

// More succinctly, using ES2015:
var newList2 = list.map(item => 
    item.get("name") === "third" ? item.set("count", 4) : item
);

console.log('newList2', newList2.toJS());
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>

Meistro
fonte
1
Obrigado por isso - tantas respostas terríveis e complicadas aqui, a resposta real é "se você quiser alterar uma lista, use o mapa". Esse é o objetivo de estruturas de dados imutáveis ​​persistentes, você não as "atualiza", cria uma nova estrutura com os valores atualizados; e é barato fazer isso porque os elementos da lista não modificados são compartilhados.
Korny # 22/17
2

Eu realmente gosto dessa abordagem no site da thomastuts :

const book = fromJS({
  title: 'Harry Potter & The Goblet of Fire',
  isbn: '0439139600',
  series: 'Harry Potter',
  author: {
    firstName: 'J.K.',
    lastName: 'Rowling'
  },
  genres: [
    'Crime',
    'Fiction',
    'Adventure',
  ],
  storeListings: [
    {storeId: 'amazon', price: 7.95},
    {storeId: 'barnesnoble', price: 7.95},
    {storeId: 'biblio', price: 4.99},
    {storeId: 'bookdepository', price: 11.88},
  ]
});

const indexOfListingToUpdate = book.get('storeListings').findIndex(listing => {
  return listing.get('storeId') === 'amazon';
});

const updatedBookState = book.setIn(['storeListings', indexOfListingToUpdate, 'price'], 6.80);

return state.set('book', updatedBookState);
Dryden Williams
fonte
-2

Você pode usar map:

list = list.map((item) => { 
    return item.get("name") === "third" ? item.set("count", 4) : item; 
});

Mas isso irá percorrer toda a coleção.

Aldo Porcallo
fonte