จะอัพเดตองค์ประกอบภายในรายการด้วย ImmutableJS ได้อย่างไร?


129

นี่คือสิ่งที่เอกสารอย่างเป็นทางการกล่าว

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>

ไม่มีทางที่นักพัฒนาเว็บทั่วไป (ไม่ใช่โปรแกรมเมอร์ที่ใช้งานได้) จะเข้าใจสิ่งนั้น!

ฉันมีกรณีที่ค่อนข้างง่าย (สำหรับวิธีการที่ไม่ใช้งานได้)

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

ฉันสามารถอัปเดตlistที่มีชื่อองค์ประกอบที่สามมีของตัวนับชุดที่ 4 ?


43
ฉันไม่รู้ว่ามันเป็นยังไง แต่เอกสารนั้นแย่มากfacebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov

71
โหวตอย่างจริงจังสำหรับการขุดที่เอกสาร หากเป้าหมายของไวยากรณ์นั้นคือทำให้ฉันรู้สึกโง่ / ไม่เพียงพอความสำเร็จที่แน่นอน อย่างไรก็ตามหากเป้าหมายคือการถ่ายทอดวิธีการทำงานที่ไม่เปลี่ยนแปลง ...
Owen

9
ฉันต้องยอมรับฉันพบว่าเอกสารสำหรับไม่เปลี่ยนรูป js นั้นน่าหงุดหงิดอย่างมาก โซลูชันที่ยอมรับสำหรับสิ่งนี้ใช้เมธอด findIndex () ที่ฉันไม่เห็นเลยในเอกสาร
RichardForrester

3
ฉันอยากให้พวกเขายกตัวอย่างสำหรับแต่ละฟังก์ชันแทนสิ่งเหล่านั้น
RedGiant

4
ตกลง เอกสารประกอบต้องเขียนโดยนักวิทยาศาสตร์บางคนเป็นฟองไม่ใช่คนที่ต้องการแสดงตัวอย่างง่ายๆ เอกสารประกอบจำเป็นต้องมีเอกสารประกอบ ตัวอย่างเช่นฉันชอบเอกสารประกอบขีดล่างซึ่งง่ายกว่ามากในการดูตัวอย่างที่ใช้ได้จริง
ReduxDJ

คำตอบ:


119

กรณีที่เหมาะสมที่สุดคือใช้ทั้งสองวิธีfindIndexและupdate

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

ปล. ไม่สามารถใช้แผนที่ได้ตลอดเวลา เช่นหากชื่อไม่ซ้ำกันและฉันต้องการอัปเดตรายการทั้งหมดที่มีชื่อเดียวกัน


1
หากคุณต้องการชื่อที่ซ้ำกันให้ใช้มัลติแมปหรือแมปที่มีทูเปิลเป็นค่า
Bergi

5
สิ่งนี้จะไม่อัปเดตองค์ประกอบสุดท้ายของรายการโดยไม่ได้ตั้งใจหากไม่มีองค์ประกอบที่มี "สาม" เป็นชื่อ? ในกรณีนั้น findIndex () จะส่งกลับ -1 ซึ่ง update () จะตีความว่าเป็นดัชนีขององค์ประกอบสุดท้าย
Sam Storie

1
ไม่ -1 ไม่ใช่องค์ประกอบสุดท้าย
Vitalii Korsakov

9
@ แซมสตอรี่พูดถูก จากเอกสาร: "ดัชนีอาจเป็นตัวเลขติดลบซึ่งดัชนีย้อนกลับจากส่วนท้ายของรายการ v.update (-1) อัปเดตรายการสุดท้ายในรายการ" facebook.github.io/immutable-js/docs/#/List/update
Gabriel Ferraz

1
ฉันไม่สามารถเรียก item.set ได้เพราะสำหรับฉันรายการไม่ใช่ประเภทไม่เปลี่ยนรูปฉันต้องแมปรายการก่อนตั้งค่าการโทรจากนั้นแปลงกลับเป็นวัตถุอีกครั้ง ดังนั้นสำหรับตัวอย่างนี้ function (item) {return Map (item) .set ("count", 4) .toObject (); }
syclee

36

ด้วย. setIn ()คุณสามารถทำได้เช่นเดียวกัน:

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

หากเราไม่ทราบดัชนีของรายการที่เราต้องการอัปเดต มันค่อนข้างง่ายที่จะค้นหาโดยใช้. findIndex () :

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

หวังว่าจะช่วยได้!


1
คุณหายไป{ }ข้างในfromJS( )โดยไม่มีวงเล็บแสดงว่าไม่ใช่ JS ที่ถูกต้อง
Andy

1
ฉันจะไม่เรียกวัตถุที่ส่งคืนว่า 'arr' เนื่องจากเป็นวัตถุ arr.elem เท่านั้นที่เป็นอาร์เรย์
Nir O

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

คำอธิบาย

การอัปเดตคอลเล็กชัน Immutable.js จะส่งคืนเวอร์ชันใหม่ของคอลเล็กชันเหล่านั้นเสมอโดยปล่อยให้ต้นฉบับไม่เปลี่ยนแปลง ด้วยเหตุนี้เราจึงไม่สามารถใช้list[2].count = 4ไวยากรณ์การกลายพันธุ์ของ JavaScript ได้ แต่เราจำเป็นต้องเรียกใช้เมธอดเหมือนกับที่เราทำกับคลาสคอลเลกชัน Java

เริ่มจากตัวอย่างที่ง่ายกว่านี้: จำนวนในรายการ

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

ตอนนี้ถ้าเราต้องการที่จะปรับปรุงรายการที่ 3 ธรรมดา JS counts[2] = 4อาร์เรย์อาจมีลักษณะเช่น: เนื่องจากเราไม่สามารถใช้การกลายพันธุ์และจำเป็นที่จะต้องเรียกวิธีการแทนเราสามารถใช้: counts.set(2, 4)- ที่หมายถึงการตั้งค่าที่ดัชนี42

การอัปเดตเชิงลึก

ตัวอย่างที่คุณให้มามีข้อมูลซ้อนกัน เราไม่สามารถใช้set()กับคอลเลกชันเริ่มต้นได้

คอลเล็กชัน Immutable.js มีกลุ่มวิธีการที่มีชื่อลงท้ายด้วย "In" ซึ่งช่วยให้คุณทำการเปลี่ยนแปลงในระดับที่ลึกกว่าในชุดที่ซ้อนกันได้ วิธีการอัปเดตทั่วไปส่วนใหญ่มีวิธี "ใน" ที่เกี่ยวข้อง ตัวอย่างเช่นสำหรับการมีset setInแทนที่จะยอมรับดัชนีหรือคีย์เป็นอาร์กิวเมนต์แรกเมธอด "In" เหล่านี้จะยอมรับ "เส้นทางคีย์" คีย์พา ธ คืออาร์เรย์ของดัชนีหรือคีย์ที่แสดงให้เห็นถึงวิธีการไปยังค่าที่คุณต้องการอัปเดต

ในตัวอย่างของคุณคุณต้องการอัปเดตรายการในรายการที่ดัชนี 2 จากนั้นจึงกำหนดค่าที่คีย์ "count" ภายในรายการนั้น ดังนั้นเส้นทางสำคัญก็[2, "count"]คือ พารามิเตอร์ที่สองของsetInวิธีการทำงานเหมือนกับsetค่าใหม่ที่เราต้องการใส่ไว้ที่นั่นดังนั้น:

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

ค้นหาเส้นทางสำคัญที่ถูกต้อง

ไปอีกขั้นหนึ่งคุณเคยบอกว่าคุณต้องการอัปเดตรายการที่ชื่อ "สาม"ซึ่งแตกต่างจากรายการที่ 3 ตัวอย่างเช่นรายการของคุณอาจไม่ได้รับการจัดเรียงหรืออาจมีการนำรายการชื่อ "two" ออกก่อนหน้านี้ นั่นหมายความว่าอันดับแรกเราต้องแน่ใจว่าเรารู้เส้นทางสำคัญที่ถูกต้องจริงๆ! สำหรับสิ่งนี้เราสามารถใช้findIndex()วิธีการ (ซึ่งโดยวิธีการทำงานเกือบจะเหมือนกับArray # findIndex )

เมื่อเราพบดัชนีในรายการซึ่งมีรายการที่เราต้องการอัปเดตเราสามารถระบุเส้นทางสำคัญไปยังค่าที่เราต้องการอัปเดตได้:

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

หมายเหตุ: SetเทียบกับUpdate

คำถามเดิมกล่าวถึงวิธีการอัพเดตมากกว่าวิธีการตั้งค่า ฉันจะอธิบายอาร์กิวเมนต์ที่สองในฟังก์ชันนั้น (เรียกว่าupdater) เนื่องจากมันแตกต่างจากset(). ในขณะที่อาร์กิวเมนต์ที่สองset()เป็นค่าใหม่ที่เราต้องการอาร์กิวเมนต์ที่สองupdate()เป็นฟังก์ชันที่ยอมรับค่าก่อนหน้าและส่งคืนค่าใหม่ที่เราต้องการ จากนั้นupdateIn()เป็นรูปแบบ "ใน" update()ที่ยอมรับเส้นทางสำคัญ

สมมติว่าเราต้องการรูปแบบของตัวอย่างของคุณที่ไม่เพียงแค่ตั้งค่าการนับเป็น4แต่เพิ่มจำนวนที่มีอยู่แทนเราสามารถจัดเตรียมฟังก์ชันที่เพิ่มค่าหนึ่งเข้าไปในค่าที่มีอยู่:

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

19

นี่คือสิ่งที่เอกสารอย่างเป็นทางการกล่าวว่า ... updateIn

คุณไม่จำเป็นต้องใช้updateInซึ่งมีไว้สำหรับโครงสร้างที่ซ้อนกันเท่านั้น คุณกำลังมองหาupdateวิธีการซึ่งมีลายเซ็นและเอกสารที่ง่ายกว่ามาก:

ส่งคืนรายการใหม่ที่มีค่าที่อัปเดตที่ดัชนีพร้อมด้วยค่าส่งคืนของการเรียกตัวอัปเดตด้วยค่าที่มีอยู่หรือ notSetValue หากไม่ได้ตั้งค่าดัชนี

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

ซึ่งตามที่Map::updateเอกสารแนะนำคือ " เทียบเท่ากับ:list.set(index, updater(list.get(index, notSetValue))) "

โดยที่องค์ประกอบที่มีชื่อ "ที่สาม"

นั่นไม่ใช่วิธีการทำงานของรายการ คุณต้องรู้ดัชนีขององค์ประกอบที่คุณต้องการอัปเดตหรือคุณต้องค้นหามัน

ฉันจะอัปเดตรายการที่องค์ประกอบที่มีชื่อที่สามตั้งค่าเป็น 4 ได้อย่างไร

สิ่งนี้ควรทำ:

list = list.update(2, function(v) {
    return {id: v.id, name: v.name, count: 4};
});

14
ไม่โครงสร้างข้อมูลที่คุณเลือกไม่เป็นไปตามตรรกะทางธุรกิจ :-) หากคุณต้องการเข้าถึงองค์ประกอบตามชื่อคุณควรใช้แผนที่แทนรายการ
Bergi

1
@bergi จริงเหรอ? ดังนั้นถ้าฉันมีรายชื่อนักเรียนในชั้นเรียนและฉันต้องการดำเนินการ CRUD กับข้อมูลนักเรียนคนนี้โดยใช้วิธีMapover a Listที่คุณจะแนะนำหรือไม่? หรือคุณพูดอย่างนั้นเพราะ OP พูดว่า "select item by name" ไม่ใช่ "select item by id"?
David Gilbertson

2
@DavidGilbertson: ครัช? ฉันขอแนะนำฐานข้อมูล :-) แต่ใช่แผนที่ id-> นักเรียนฟังดูเหมาะสมกว่ารายการเว้นแต่คุณจะได้รับคำสั่งจากพวกเขาและต้องการเลือกตามดัชนี
Bergi

1
@bergi ฉันเดาว่าเราจะเห็นด้วยไม่เห็นด้วยฉันได้รับข้อมูลมากมายจาก API ในรูปแบบของอาร์เรย์ของสิ่งต่างๆที่มี ID ซึ่งฉันต้องอัปเดตสิ่งเหล่านั้นด้วย ID นี่คือ JS Array และในความคิดของฉันควรเป็นรายการ JS ที่ไม่เปลี่ยนรูป
David Gilbertson

1
@DavidGilbertson: ฉันคิดว่า API เหล่านั้นใช้อาร์เรย์ JSON สำหรับชุดซึ่งไม่ได้เรียงลำดับอย่างชัดเจน
Bergi

12

ใช้. 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>


1
ขอบคุณสำหรับคำตอบที่ซับซ้อนน่ากลัวมากมายที่นี่คำตอบที่แท้จริงคือ "ถ้าคุณต้องการเปลี่ยนรายการให้ใช้แผนที่" นั่นคือจุดรวมของโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบถาวรคุณไม่ได้ "อัปเดต" พวกเขาคุณสร้างโครงสร้างใหม่โดยมีค่าที่อัปเดตอยู่ในนั้น และมีราคาถูกที่จะทำเช่นนั้นเนื่องจากมีการแชร์องค์ประกอบรายการที่ไม่ได้แก้ไข
Korny

2

ฉันชอบแนวทางนี้มากจากเว็บไซต์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);

-2

คุณสามารถใช้map:

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

แต่จะทำซ้ำตลอดทั้งคอลเลคชัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.