¿Cómo actualizar el elemento dentro de la Lista con ImmutableJS?

129

Esto es lo que dijeron los documentos oficiales

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>

¡No hay forma de que un desarrollador web normal (no un programador funcional) lo entienda!

Tengo un caso bastante simple (para un enfoque no 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);

¿Cómo puedo actualizar listdonde el elemento con nombre tercero tiene su recuento establecido en 4 ?

Vitalii Korsakov
fuente
43
No sé cómo es, pero la documentación es terrible facebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov
71
Voto serio para la excavación en la documentación. Si el objetivo de esa sintaxis es hacerme sentir tonto / inadecuado, éxito definitivo. Si el objetivo, sin embargo, es transmitir cómo funciona inmutables, así ...
Owen
9
Tengo que estar de acuerdo, encuentro que la documentación de immutable.js es muy frustrante. La solución aceptada para esto utiliza un método findIndex () que ni siquiera veo en los documentos.
RichardForrester
3
Prefiero que den un ejemplo para cada función en lugar de esas cosas.
RedGiant
44
Convenido. La documentación debe haber sido escrita por un científico en una burbuja, no por alguien que quiera mostrar algunos ejemplos simples. La documentación necesita algo de documentación. Por ejemplo, me gusta la documentación de subrayado, mucho más fácil de ver ejemplos prácticos.
ReduxDJ

Respuestas:

119

El caso más apropiado es usar ambos findIndexy updatemétodos.

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

PD: No siempre es posible usar Maps. Por ejemplo, si los nombres no son únicos y quiero actualizar todos los elementos con los mismos nombres.

Vitalii Korsakov
fuente
1
Si necesita nombres duplicados, use mapas múltiples o mapas con tuplas como valores.
Bergi
55
¿Esto no actualizará inadvertidamente el último elemento de la lista si no hay ningún elemento con "tres" como nombre? En ese caso, findIndex () devolvería -1, que update () interpretaría como el índice del último elemento.
Sam Storie
1
No, -1 no es el último elemento
Vitalii Korsakov
9
@Sam Storie tiene razón. De los documentos: "el índice puede ser un número negativo, que indexa desde el final de la Lista. V.update (-1) actualiza el último elemento de la Lista". facebook.github.io/immutable-js/docs/#/List/update
Gabriel Ferraz
1
No pude llamar a item.set porque para mí el elemento no era del tipo Inmutable, primero tuve que asignar el elemento, llamar al conjunto y luego convertirlo nuevamente en objeto. Entonces, para este ejemplo, function (item) {return Map (item) .set ("count", 4) .toObject (); }
syclee
36

Con .setIn () puedes hacer lo mismo:

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);

Si no conocemos el índice de la entrada que queremos actualizar. Es bastante fácil encontrarlo usando .findIndex () :

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

¡Espero eso ayude!

Albert Olivé
fuente
1
te falta por { }dentro fromJS( ), sin las llaves no es válido JS.
Andy
1
No llamaría al objeto devuelto 'arr' ya que es un objeto. solo arr.elem es una matriz
Nir O.
21
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

Explicación

La actualización de las colecciones Immutable.js siempre devuelve nuevas versiones de esas colecciones dejando el original sin cambios. Por eso, no podemos usar la list[2].count = 4sintaxis de mutación de JavaScript . En su lugar, necesitamos llamar a métodos, como lo haríamos con las clases de colección de Java.

Comencemos con un ejemplo más simple: solo los recuentos en una lista.

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

Ahora bien, si queremos actualizar la tercera tema, una matriz de JS sencillo puede ser: counts[2] = 4. Como no podemos usar la mutación y necesitamos llamar a un método, podemos usar: counts.set(2, 4)eso significa establecer el valor 4en el índice 2.

Actualizaciones profundas

Sin embargo, el ejemplo que dio tiene datos anidados. No podemos simplemente usar set()en la colección inicial.

Las colecciones Immutable.js tienen una familia de métodos con nombres que terminan en "In" que le permiten realizar cambios más profundos en un conjunto anidado. Los métodos de actualización más comunes tienen un método "In" relacionado. Por ejemplo para sethay setIn. En lugar de aceptar un índice o una clave como primer argumento, estos métodos "En" aceptan una "ruta de clave". La ruta de la clave es una matriz de índices o claves que ilustra cómo llegar al valor que desea actualizar.

En su ejemplo, deseaba actualizar el elemento en la lista en el índice 2, y luego el valor en la clave "contar" dentro de ese elemento. Entonces el camino clave sería [2, "count"]. El segundo parámetro del setInmétodo funciona igual set, es el nuevo valor que queremos poner allí, así que:

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

Encontrar el camino clave correcto

Yendo un paso más allá, en realidad dijiste que querías actualizar el elemento donde el nombre es "tres", que es diferente al solo tercer elemento. Por ejemplo, ¿tal vez su lista no está ordenada, o tal vez allí el elemento llamado "dos" fue eliminado anteriormente? ¡Eso significa que primero debemos asegurarnos de que realmente conocemos la ruta de clave correcta! Para esto podemos usar el findIndex()método (que, por cierto, funciona casi exactamente como Array # findIndex ).

Una vez que hayamos encontrado el índice en la lista que contiene el elemento que queremos actualizar, podemos proporcionar la ruta clave al valor que deseamos actualizar:

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

NB: SetvsUpdate

La pregunta original menciona los métodos de actualización en lugar de los métodos establecidos. Explicaré el segundo argumento en esa función (llamada updater), ya que es diferente de set(). Mientras que el segundo argumento set()es el nuevo valor que queremos, el segundo argumento update()es una función que acepta el valor anterior y devuelve el nuevo valor que queremos. Entonces, updateIn()es la variación "In" de la update()cual acepta una ruta clave.

Digamos, por ejemplo, que queremos una variación de su ejemplo que no solo establezca el recuento 4, sino que incremente el recuento existente, podríamos proporcionar una función que agregue uno al valor existente:

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

Esto es lo que dijeron los documentos oficiales ... updateIn

No es necesario updateIn, que es solo para estructuras anidadas. Está buscando el updatemétodo , que tiene una firma y documentación mucho más simple:

Devuelve una nueva Lista con un valor actualizado en el índice con el valor devuelto de llamar al actualizador con el valor existente, o notSetValue si no se estableció el índice.

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

que, como sugieren los Map::updatedocumentos , es " equivalente a:list.set(index, updater(list.get(index, notSetValue))) ".

donde elemento con nombre "tercero"

Así no es como funcionan las listas. Debe conocer el índice del elemento que desea actualizar o debe buscarlo.

¿Cómo puedo actualizar la lista donde el elemento con nombre tercero tiene su recuento establecido en 4?

Esto debería hacerlo:

list = list.update(2, function(v) {
    return {id: v.id, name: v.name, count: 4};
});
Bergi
fuente
14
No, su elección de estructura de datos no satisface la lógica empresarial :-) Si desea acceder a los elementos por su nombre, debe usar un mapa en lugar de una lista.
Bergi
1
@bergi, ¿en serio? Entonces, si tengo, digamos, una lista de estudiantes en una clase, y quiero hacer operaciones CRUD en los datos de este estudiante, usar un Mapsobre Listes el enfoque que sugeriría. ¿O solo dice eso porque OP dijo "seleccionar elemento por nombre" y no "seleccionar elemento por id"?
David Gilbertson el
2
@DavidGilbertson: ¿CRUDO? Sugeriría una base de datos :-) Pero sí, un mapa de id-> estudiante suena más apropiado que una lista, a menos que tenga un pedido y desee seleccionarlos por índice.
Bergi
1
@bergi Creo que aceptaremos estar en desacuerdo, recibo muchos datos de las API en forma de matrices de cosas con ID, donde necesito actualizar esas cosas por ID. Esta es una matriz JS y, en mi opinión, debería ser una lista JS inmutable.
David Gilbertson el
1
@DavidGilbertson: Creo que esas API toman matrices JSON para conjuntos, que están explícitamente 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
fuente
1
Gracias por eso, hay tantas respuestas complicadas terribles aquí, la respuesta real es "si quieres cambiar una lista, usa el mapa". Ese es el objetivo de las estructuras de datos inmutables persistentes, no las "actualiza", crea una nueva estructura con los valores actualizados; y es barato hacerlo porque los elementos de la lista no modificada se comparten.
Korny
2

Realmente me gusta este enfoque del sitio web de 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
fuente
-2

Puedes usar map:

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

Pero esto se repetirá en toda la colección.

Aldo Porcallo
fuente