Bagaimana cara memperbarui elemen di dalam Daftar dengan ImmutableJS?

129

Inilah yang dikatakan oleh dokumen resmi

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>

Tidak mungkin pengembang web normal (bukan programmer fungsional) akan mengerti itu!

Saya memiliki kasus yang cukup sederhana (untuk pendekatan non-fungsional).

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

Bagaimana saya dapat memperbarui di listmana elemen dengan nama ketiga memiliki count set ke 4 ?

Vitalii Korsakov
sumber
43
Saya tidak tahu seperti apa bentuknya, tetapi dokumentasinya mengerikan facebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov
71
Suara positif yang serius untuk penggalian di dokumentasi. Jika tujuan dari sintaks itu adalah untuk membuat saya merasa bodoh / tidak mampu, pasti sukses. Namun, jika tujuannya adalah untuk menyampaikan cara kerja Immutable, yah ...
Owen
9
Saya harus setuju, saya menemukan dokumentasi untuk immutable.js menjadi sangat frustasi. Solusi yang diterima untuk ini menggunakan metode findIndex () yang bahkan tidak saya lihat sama sekali dalam dokumen.
RichardForrester
3
Saya lebih suka mereka memberikan contoh untuk setiap fungsi daripada hal-hal itu.
RedGiant
4
Sepakat. Dokumentasi pasti ditulis oleh beberapa ilmuwan dalam sebuah gelembung, bukan oleh seseorang yang ingin menunjukkan beberapa contoh sederhana. Dokumentasi membutuhkan beberapa dokumentasi. Sebagai contoh, saya suka dokumentasi garis bawah, lebih mudah untuk melihat contoh praktis.
ReduxDJ

Jawaban:

119

Kasus yang paling tepat adalah menggunakan keduanya findIndexdan updatemetode.

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

PS Tidak selalu mungkin menggunakan Maps. Misalnya, jika nama tidak unik dan saya ingin memperbarui semua item dengan nama yang sama.

Vitalii Korsakov
sumber
1
Jika Anda memerlukan nama duplikat, gunakan multimaps - atau peta dengan tupel sebagai nilai.
Bergi
5
Tidakkah ini akan secara tidak sengaja memperbarui elemen terakhir dari daftar jika tidak ada elemen dengan "tiga" sebagai namanya? Dalam hal ini findIndex () akan mengembalikan -1, pembaruan mana () akan menafsirkan sebagai indeks dari elemen terakhir.
Sam Storie
1
Tidak, -1 bukan elemen terakhir
Vitalii Korsakov
9
@Sam Storie benar. Dari dokumen: "indeks mungkin angka negatif, yang mengindeks kembali dari akhir Daftar. V.update (-1) memperbarui item terakhir dalam Daftar." facebook.github.io/immutable-js/docs/#/List/update
Gabriel Ferraz
1
Saya tidak dapat memanggil item.set karena bagi saya item bukanlah tipe yang tidak dapat diubah, saya harus memetakan item terlebih dahulu, memanggil set dan kemudian mengubahnya kembali ke objek lagi. Jadi untuk contoh ini, function (item) {return Map (item) .set ("count", 4) .toObject (); }
syclee
36

Dengan .setIn () Anda dapat melakukan hal yang sama:

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

Jika kami tidak tahu indeks entri yang ingin kami perbarui. Cukup mudah untuk menemukannya menggunakan .findIndex () :

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

Semoga ini bisa membantu!

Albert Olivé
sumber
1
Anda hilang { }di dalam fromJS( ), tanpa kawat gigi itu tidak valid JS.
Andy
1
Saya tidak akan menyebut objek yang dikembalikan 'arr' karena ini adalah objek. hanya arr.elem adalah array
Nir O.
21
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

Penjelasan

Memperbarui koleksi Immutable.js selalu mengembalikan versi baru dari koleksi tersebut tanpa meninggalkan perubahan. Karena itu, kami tidak dapat menggunakan JavaScriptlist[2].count = 4 sintaks mutasi . Sebaliknya kita perlu memanggil metode, seperti yang mungkin kita lakukan dengan kelas koleksi Java.

Mari kita mulai dengan contoh yang lebih sederhana: hitung saja dalam daftar.

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

Sekarang jika kita ingin memperbarui item 3, JS array yang polos akan terlihat seperti: counts[2] = 4. Karena kita tidak dapat menggunakan mutasi, dan perlu memanggil metode, sebaliknya kita dapat menggunakan: counts.set(2, 4)- itu berarti mengatur nilai 4pada indeks2 .

Pembaruan mendalam

Contoh yang Anda berikan memiliki data bersarang. Kami tidak bisa hanya menggunakanset() koleksi awal.

Koleksi Immutable.js memiliki kumpulan metode dengan nama yang diakhiri dengan "In" yang memungkinkan Anda untuk membuat perubahan yang lebih dalam pada kumpulan yang disarangkan. Metode pemutakhiran yang paling umum memiliki metode "Dalam" yang terkait. Misalnya setadasetIn . Alih-alih menerima indeks atau kunci sebagai argumen pertama, metode "Dalam" ini menerima "jalur kunci". Jalur kunci adalah larik indeks atau kunci yang menggambarkan cara mencapai nilai yang ingin Anda perbarui.

Dalam contoh Anda, Anda ingin memperbarui item dalam daftar di indeks 2, dan kemudian nilai pada kunci "hitung" dalam item itu. Jadi jalan kuncinya adalah [2, "count"]. Parameter kedua untuk setInmetode ini berfungsi seperti set, ini adalah nilai baru yang ingin kita taruh di sana, jadi:

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

Menemukan jalur kunci yang tepat

Selangkah lebih maju, Anda sebenarnya mengatakan Anda ingin memperbarui item yang namanya "tiga" yang berbeda dari item ke-3. Misalnya, mungkin daftar Anda tidak diurutkan, atau mungkin ada item bernama "dua" telah dihapus sebelumnya? Itu berarti pertama-tama kita perlu memastikan kita benar-benar mengetahui jalur kunci yang benar! Untuk ini kita dapat menggunakan findIndex()metode (yang, omong-omong, bekerja hampir persis seperti Array # findIndex ).

Setelah kami menemukan indeks dalam daftar yang memiliki item yang ingin kami perbarui, kami dapat memberikan jalur kunci ke nilai yang ingin kami perbarui:

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

NB: SetvsUpdate

Pertanyaan aslinya menyebutkan metode pembaruan daripada metode yang ditetapkan. Saya akan menjelaskan argumen kedua dalam fungsi itu (dipanggil updater), karena berbeda dari set(). Sementara argumen kedua set()adalah nilai baru yang kita inginkan, argumen kedua update()adalah fungsi yang menerima nilai sebelumnya dan mengembalikan nilai baru yang kita inginkan. Kemudian, updateIn()adalah variasi "Dalam" dariupdate() yang menerima jalur kunci.

Misalnya, kami ingin variasi contoh Anda yang tidak hanya mengatur penghitungan 4, tetapi sebagai tambahan penghitungan yang ada, kami dapat menyediakan fungsi yang menambahkan satu ke nilai yang ada:

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

Inilah yang dikatakan dokumen resmi ... updateIn

Anda tidak perlu updateIn, yang hanya untuk struktur bersarang. Anda sedang mencariupdate metode ini , yang memiliki tanda tangan dan dokumentasi yang jauh lebih sederhana:

Mengembalikan daftar baru dengan nilai yang diperbarui pada indeks dengan nilai kembali memanggil pembaru dengan nilai yang ada, atau notSetValue jika indeks tidak ditetapkan.

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

yang, seperti yang disarankan Map::updatedokumen , adalah " setara dengan:list.set(index, updater(list.get(index, notSetValue))) ".

di mana elemen dengan nama "ketiga"

Bukan itu cara kerja daftar. Anda harus mengetahui indeks elemen yang ingin Anda perbarui, atau Anda harus mencarinya.

Bagaimana saya bisa memperbarui daftar di mana elemen dengan nama ketiga ditetapkan ke 4?

Ini harus dilakukan:

list = list.update(2, function(v) {
    return {id: v.id, name: v.name, count: 4};
});
Bergi
sumber
14
Tidak, struktur data pilihan Anda tidak memenuhi logika bisnis :-) Jika Anda ingin mengakses elemen dengan namanya, Anda harus menggunakan peta dan bukan daftar.
Bergi
1
@bergi, benarkah? Jadi jika saya memiliki, katakanlah, daftar siswa di kelas, dan saya ingin melakukan operasi CRUD pada data siswa ini, menggunakan Maplebih dari satu Listadalah pendekatan yang Anda sarankan? Atau apakah Anda hanya mengatakan itu karena OP mengatakan "pilih item dengan nama" dan bukan "pilih item dengan id"?
David Gilbertson
2
@DavidGilbertson: CRUD? Saya akan menyarankan database :-) Tapi ya, id-> peta siswa terdengar lebih tepat daripada daftar, kecuali Anda sudah memesannya dan ingin memilihnya berdasarkan indeks.
Bergi
1
@bergi Saya kira kita akan setuju untuk tidak setuju, saya mendapatkan banyak data kembali dari API dalam bentuk array hal-hal dengan ID, di mana saya perlu memperbarui hal-hal itu dengan ID. Ini adalah JS Array, dan dalam pikiran saya, karena itu harus menjadi daftar JS tetap.
David Gilbertson
1
@ DavidGilbertson: Saya pikir API mengambil array JSON untuk set - yang secara eksplisit unordered.
Bergi
12

Gunakan .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
sumber
1
Terima kasih untuk itu - begitu banyak jawaban berbelit-belit yang mengerikan di sini, jawaban sebenarnya adalah "jika Anda ingin mengubah daftar, gunakan peta". Itulah inti dari struktur data yang tidak dapat diubah, Anda tidak "memperbarui" mereka, Anda membuat struktur baru dengan nilai yang diperbarui di dalamnya; dan murah untuk melakukannya karena elemen daftar yang tidak dimodifikasi dibagikan.
Korny
2

Saya sangat menyukai pendekatan ini dari situs web 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
sumber
-2

Anda bisa menggunakan map:

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

Tapi ini akan mengulangi seluruh koleksi.

Aldo Porcallo
sumber