นี่เป็นวิธีที่ถูกต้องในการลบรายการโดยใช้ redux หรือไม่?


118

ฉันรู้ว่าฉันไม่ควรกลายพันธุ์อินพุตและควรโคลนวัตถุเพื่อกลายพันธุ์ ฉันปฏิบัติตามแบบแผนที่ใช้กับโครงการเริ่มต้น redux ซึ่งใช้:

ADD_ITEM: (state, action) => ({
  ...state,
  items: [...state.items, action.payload.value],
  lastUpdated: action.payload.date
})

สำหรับการเพิ่มรายการ - ฉันได้รับการใช้การแพร่กระจายเพื่อต่อท้ายรายการในอาร์เรย์

สำหรับการลบฉันใช้:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: [...state.items.splice(0, action.payload), ...state.items.splice(1)],
  lastUpdated: Date.now() 
})

แต่นี่เป็นการกลายพันธุ์วัตถุสถานะอินพุต - สิ่งนี้ต้องห้ามแม้ว่าฉันจะส่งคืนวัตถุใหม่หรือไม่


1
คำถามอย่างรวดเร็ว. Splice ส่งคืนรายการที่คุณนำออก นั่นคือความตั้งใจของคุณหรือไม่? ถ้าไม่คุณควรใช้สไลซ์ซึ่งปฏิบัติตามกฎหมายไม่ให้กลายพันธุ์
m0meni

ในตัวอย่างนี้ฉันเชื่อมสองส่วนของอาร์เรย์เข้าด้วยกันเป็นอาร์เรย์ใหม่โดยมีรายการที่ฉันต้องการลบทิ้งไว้ Slice ยังคืนไอเทมที่ถูกลบใช่ไหม เพียง แต่ทำเช่นนั้นโดยไม่เปลี่ยนอาร์เรย์เดิมดังนั้นจึงเป็นแนวทางที่ดีกว่าหรือไม่?
CWright

@ AR7 ตามคำแนะนำของคุณ: items: [...state.items.slice(0, action.payload.value), ...state.items.slice(action.payload.value + 1 )]ตอนนี้ใช้ slice แทนการประกบเพื่อไม่ให้อินพุตกลายพันธุ์ - นี่เป็นวิธีที่จะไปหรือมีวิธีที่กระชับกว่านี้?
CWright

คำตอบ:


209

ไม่อย่าเปลี่ยนสถานะของคุณ

แม้ว่าคุณจะส่งคืนวัตถุใหม่ แต่คุณก็ยังคงสร้างมลพิษให้กับวัตถุเก่าซึ่งคุณไม่ต้องการทำ สิ่งนี้ทำให้เกิดปัญหาเมื่อทำการเปรียบเทียบระหว่างรัฐเก่าและรัฐใหม่ ตัวอย่างเช่นshouldComponentUpdateที่ react-redux ใช้ภายใต้ประทุน นอกจากนี้ยังทำให้การเดินทางข้ามเวลาเป็นไปไม่ได้ (เช่นเลิกทำและทำซ้ำ)

ให้ใช้วิธีที่ไม่เปลี่ยนรูปแทน มักจะใช้และไม่เคยArray#sliceArray#splice

ฉันถือว่ารหัสของคุณaction.payloadเป็นดัชนีของรายการที่ถูกลบออก วิธีที่ดีกว่าจะเป็นดังนี้:

items: [
    ...state.items.slice(0, action.payload),
    ...state.items.slice(action.payload + 1)
],

สิ่งนี้จะไม่ได้ผลหากเรากำลังจัดการกับองค์ประกอบสุดท้ายในอาร์เรย์นอกจากนี้การใช้...ในคำสั่งที่สองจะเพิ่มเนื้อหาของรัฐของคุณเป็นสองเท่า
Thaenor

4
โปรดพิสูจน์ด้วยตัวอย่าง jsfiddle / codepen ตัวอย่างข้อมูลarr.slice(arr.length)ควรสร้างอาร์เรย์ว่างเสมอไม่ว่าเนื้อหาจะเป็นarrอย่างไร
David

1
@ david-l-walsh ขออภัยในความสับสนฉันต้องพิมพ์ผิดหรืออะไรบางอย่างเมื่อทดสอบตัวอย่างนี้ มันทำงานได้อย่างมหัศจรรย์ คำถามเดียวของฉันคือทำไมความต้องการของตัวดำเนินการกระจาย...ในส่วนที่สอง -...state.items.slice(action.payload + 1)
Thaenor

5
Array#sliceส่งคืนอาร์เรย์ ในการรวมสองชิ้นเป็นอาร์เรย์เดียวฉันใช้ตัวดำเนินการกระจาย หากไม่มีคุณจะมีอาร์เรย์อาร์เรย์
David L. Walsh

4
ที่สมเหตุสมผล ขอบคุณมากสำหรับการชี้แจง (และขออภัยในความสับสนในตอนแรก)
Thaenor

151

คุณสามารถใช้วิธีการกรองอาร์เรย์เพื่อลบองค์ประกอบเฉพาะออกจากอาร์เรย์โดยไม่ต้องเปลี่ยนสถานะเดิม

return state.filter(element => element !== action.payload);

ในบริบทของโค้ดของคุณจะมีลักษณะดังนี้:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => item !== action.payload),
  lastUpdated: Date.now() 
})

1
ตัวกรองสร้างอาร์เรย์ใหม่หรือไม่
chenop

6
@chenop ใช่เมธอด Array.filter จะส่งคืนอาร์เรย์ใหม่ developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Steph M

4
โปรดทราบว่าหากมีรายการที่ซ้ำกันการดำเนินการนี้จะลบรายการทั้งหมดออก หากต้องการใช้ตัวกรองเพื่อลบดัชนีเฉพาะคุณสามารถใช้เช่นarr.filter((val, i) => i !== action.payload )
erich2k8

22

Array.prototype.filterวิธีES6 ส่งคืนอาร์เรย์ใหม่พร้อมรายการที่ตรงกับเกณฑ์ ดังนั้นในบริบทของคำถามเดิมสิ่งนี้จะเป็น:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => action.payload !== item),
  lastUpdated: Date.now() 
})

.filter()ไม่ใช่วิธี ES2015 แต่ถูกเพิ่มใน ES5 เวอร์ชันก่อนหน้า
morkro

7

อีกรูปแบบหนึ่งของตัวลด "DELETED" ที่ไม่เปลี่ยนรูปสำหรับอาร์เรย์ที่มีวัตถุ:

const index = state.map(item => item.name).indexOf(action.name);
const stateTemp = [
  ...state.slice(0, index),
  ...state.slice(index + 1)
];
return stateTemp;

0

กฎทองคือเราจะไม่คืนสถานะที่กลายพันธุ์ แต่เป็นสถานะใหม่ ขึ้นอยู่กับประเภทของการกระทำของคุณคุณอาจต้องอัปเดตแผนผังสถานะของคุณในรูปแบบต่างๆเมื่อมันกระทบตัวลด

ในสถานการณ์นี้เรากำลังพยายามลบรายการออกจากทรัพย์สินของรัฐ

สิ่งนี้นำเราไปสู่แนวคิดของรูปแบบการอัปเดตที่ไม่เปลี่ยนรูป (หรือการแก้ไขข้อมูล) ของ Redux ความไม่เปลี่ยนรูปเป็นกุญแจสำคัญเนื่องจากเราไม่ต้องการเปลี่ยนค่าในแผนภูมิสถานะโดยตรง แต่ควรทำสำเนาและส่งคืนค่าใหม่ตามค่าเก่าเสมอ

นี่คือตัวอย่างของวิธีการลบวัตถุที่ซ้อนกัน:

// ducks/outfits (Parent)

// types
export const NAME = `@outfitsData`;
export const REMOVE_FILTER = `${NAME}/REMOVE_FILTER`;

// initialization
const initialState = {
  isInitiallyLoaded: false,
  outfits: ['Outfit.1', 'Outfit.2'],
  filters: {
    brand: [],
    colour: [],
  },
  error: '',
};

// action creators
export function removeFilter({ field, index }) {
  return {
    type: REMOVE_FILTER,
    field,
    index,
  };
}

export default function reducer(state = initialState, action = {}) {
  sswitch (action.type) {  
  case REMOVE_FILTER:
  return {
    ...state,
    filters: {
    ...state.filters,
       [action.field]: [...state.filters[action.field]]
       .filter((x, index) => index !== action.index)
    },
  };
  default:
     return state;
  }
}

เพื่อให้เข้าใจสิ่งนี้ได้ดีขึ้นโปรดอ่านบทความนี้: https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da

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