วิธีอัปเดตค่าเดียวภายในรายการอาร์เรย์เฉพาะใน redux


107

ฉันมีปัญหาที่การเรนเดอร์สถานะทำให้เกิดปัญหา ui และได้รับการแนะนำให้อัปเดตเฉพาะค่าเฉพาะภายในตัวลดของฉันเพื่อลดจำนวนการแสดงผลบนเพจ

นี่คือตัวอย่างของรัฐของฉัน

{
 name: "some name",
 subtitle: "some subtitle",
 contents: [
   {title: "some title", text: "some text"},
   {title: "some other title", text: "some other text"}
 ]
}

และฉันกำลังอัปเดตเป็นแบบนี้

case 'SOME_ACTION':
   return { ...state, contents: action.payload }

ที่action.payloadเป็นอาร์เรย์ทั้งหมดที่มีค่าใหม่ แต่ตอนนี้ฉันแค่ต้องอัปเดตข้อความของรายการที่สองในอาร์เรย์เนื้อหาและสิ่งนี้ไม่ได้ผล

case 'SOME_ACTION':
   return { ...state, contents[1].text: action.payload }

ที่action.payloadตอนนี้เป็นข้อความที่ฉันต้องการสำหรับการปรับปรุง

คำตอบ:


60

คุณสามารถใช้ตัวช่วย React Immutability

import update from 'react-addons-update';

// ...    

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      1: {
        text: {$set: action.payload}
      }
    }
  });

แม้ว่าฉันจะคิดว่าคุณอาจจะทำอะไรแบบนี้มากกว่านี้?

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      [action.id]: {
        text: {$set: action.payload}
      }
    }
  });

ฉันจำเป็นต้องรวมตัวช่วยเหล่านี้จากการตอบสนองภายในตัวลดหรือไม่?
Ilja

8
แม้ว่าแพ็คเกจจะย้ายไปที่kolodny/immutability-helperแล้วก็ตามตอนนี้yarn add immutability-helperและimport update from 'immutability-helper';
SacWebDeveloper

กรณีของฉันเหมือนกันทุกประการ แต่เมื่อฉันอัปเดตออบเจ็กต์ในองค์ประกอบใดองค์ประกอบหนึ่งในอาร์เรย์ค่าที่อัปเดตจะไม่สะท้อนใน componentWillReceiveProps ฉันใช้เนื้อหาโดยตรงใน mapStateToProps ของฉันเราต้องใช้อย่างอื่นใน mapStateToProps เพื่อให้มันสะท้อน?
Srishti Gupta

175

คุณสามารถใช้map. นี่คือตัวอย่างการใช้งาน:

case 'SOME_ACTION':
   return { 
       ...state, 
       contents: state.contents.map(
           (content, i) => i === 1 ? {...content, text: action.payload}
                                   : content
       )
    }

นั่นเป็นวิธีที่ฉันทำเช่นกันฉันไม่แน่ใจว่าเป็นแนวทางที่ดีหรือไม่เนื่องจากแผนที่จะส่งคืนอาร์เรย์ใหม่ ความคิดใด ๆ ?
Thiago C.S Ventura

@ Ventura ใช่มันเป็นสิ่งที่ดีเพราะมันสร้างการอ้างอิงใหม่สำหรับอาร์เรย์และวัตถุใหม่ธรรมดาสำหรับสิ่งที่ตั้งใจจะอัปเดต (และมีเพียงอันเดียวเท่านั้น) ซึ่งเป็นสิ่งที่คุณต้องการ
ivcandela

3
ฉันคิดว่านี่เป็นสิ่งที่ดีที่สุด
Jesus Gomez

12
ฉันทำสิ่งนี้บ่อยเช่นกัน แต่ดูเหมือนว่าจะมีค่าใช้จ่ายในการคำนวณในการคำนวณองค์ประกอบทั้งหมดมากกว่าการอัปเดตดัชนีที่จำเป็น
Ben Creasy

21

คุณไม่จำเป็นต้องทำทุกอย่างในบรรทัดเดียว:

case 'SOME_ACTION':
  const newState = { ...state };
  newState.contents = 
    [
      newState.contents[0],
      {title: newState.contnets[1].title, text: action.payload}
    ];
  return newState

1
คุณพูดถูกฉันแก้ไขอย่างรวดเร็ว btw อาร์เรย์มีความยาวคงที่หรือไม่? และดัชนีที่คุณต้องการแก้ไขได้รับการแก้ไขด้วยหรือไม่? แนวทางปัจจุบันใช้ได้เฉพาะกับอาร์เรย์ขนาดเล็กและมีดัชนีคงที่
Yuya

2
ห่อเนื้อความของคุณใน {} เนื่องจาก const เป็นขอบเขตการบล็อก
Benny Powers

15

ไปปาร์ตี้ช้ามาก แต่นี่คือวิธีแก้ปัญหาทั่วไปที่ใช้ได้กับทุกค่าดัชนี

  1. คุณสร้างและกระจายอาร์เรย์ใหม่จากอาร์เรย์เก่าจนถึงที่indexคุณต้องการเปลี่ยนแปลง

  2. เพิ่มข้อมูลที่คุณต้องการ

  3. สร้างและกระจายอาร์เรย์ใหม่จากที่indexคุณต้องการเปลี่ยนไปยังจุดสิ้นสุดของอาร์เรย์

let index=1;// probabbly action.payload.id
case 'SOME_ACTION':
   return { 
       ...state, 
       contents: [
          ...state.contents.slice(0,index),
          {title: "some other title", text: "some other text"},
         ...state.contents.slice(index+1)
         ]
    }

อัปเดต:

ฉันได้สร้างโมดูลเล็ก ๆ เพื่อทำให้โค้ดง่ายขึ้นดังนั้นคุณต้องเรียกใช้ฟังก์ชัน:

case 'SOME_ACTION':
   return {
       ...state,
       contents: insertIntoArray(state.contents,index, {title: "some title", text: "some text"})
    }

สำหรับตัวอย่างเพิ่มเติมโปรดดูที่ที่เก็บ

ลายเซ็นฟังก์ชัน:

insertIntoArray(originalArray,insertionIndex,newData)

4

ฉันเชื่อว่าเมื่อคุณต้องการการดำเนินการประเภทนี้ในสถานะ Redux ของคุณตัวดำเนินการสเปรดคือเพื่อนของคุณและหลักการนี้ใช้กับเด็กทุกคน

สมมติว่านี่เป็นสถานะของคุณ:

const state = {
    houses: {
        gryffindor: {
          points: 15
        },
        ravenclaw: {
          points: 18
        },
        hufflepuff: {
          points: 7
        },
        slytherin: {
          points: 5
        }
    }
}

และคุณต้องการเพิ่ม 3 คะแนนให้กับเรเวนคลอ

const key = "ravenclaw";
  return {
    ...state, // copy state
    houses: {
      ...state.houses, // copy houses
      [key]: {  // update one specific house (using Computed Property syntax)
        ...state.houses[key],  // copy that specific house's properties
        points: state.houses[key].points + 3   // update its `points` property
      }
    }
  }

ด้วยการใช้ตัวดำเนินการการแพร่กระจายคุณสามารถอัปเดตเฉพาะสถานะใหม่โดยปล่อยให้ทุกอย่างไม่เสียหาย

ตัวอย่างจากบทความที่น่าทึ่งนี้คุณสามารถค้นหาตัวเลือกที่เป็นไปได้เกือบทั้งหมดพร้อมตัวอย่างที่ยอดเยี่ยม


4

ในกรณีของฉันฉันทำสิ่งนี้ตามคำตอบของ Luis:

...State object...
userInfo = {
name: '...',
...
}

...Reducer's code...
case CHANGED_INFO:
return {
  ...state,
  userInfo: {
    ...state.userInfo,
    // I'm sending the arguments like this: changeInfo({ id: e.target.id, value: e.target.value }) and use them as below in reducer!
    [action.data.id]: action.data.value,
  },
};

0

นี่คือวิธีที่ฉันทำเพื่อหนึ่งในโครงการของฉัน:

const markdownSaveActionCreator = (newMarkdownLocation, newMarkdownToSave) => ({
  type: MARKDOWN_SAVE,
  saveLocation: newMarkdownLocation,
  savedMarkdownInLocation: newMarkdownToSave  
});

const markdownSaveReducer = (state = MARKDOWN_SAVED_ARRAY_DEFAULT, action) => {
  let objTemp = {
    saveLocation: action.saveLocation, 
    savedMarkdownInLocation: action.savedMarkdownInLocation
  };

  switch(action.type) {
    case MARKDOWN_SAVE:
      return( 
        state.map(i => {
          if (i.saveLocation === objTemp.saveLocation) {
            return Object.assign({}, i, objTemp);
          }
          return i;
        })
      );
    default:
      return state;
  }
};

0

ฉันกลัวว่าการใช้map()วิธีการของอาร์เรย์อาจมีราคาแพงเนื่องจากอาร์เรย์ทั้งหมดจะต้องทำซ้ำ แต่ฉันรวมอาร์เรย์ใหม่ที่ประกอบด้วยสามส่วน:

  • ส่วนหัว - รายการก่อนรายการแก้ไข
  • รายการที่แก้ไข
  • หาง - ไอเทมหลังไอเท็มแก้ไข

นี่คือตัวอย่างที่ฉันใช้ในโค้ดของฉัน (NgRx แต่กลไกเหมือนกันสำหรับการใช้งาน Redux อื่น ๆ ):

// toggle done property: true to false, or false to true

function (state, action) {
    const todos = state.todos;
    const todoIdx = todos.findIndex(t => t.id === action.id);

    const todoObj = todos[todoIdx];
    const newTodoObj = { ...todoObj, done: !todoObj.done };

    const head = todos.slice(0, todoIdx - 1);
    const tail = todos.slice(todoIdx + 1);
    const newTodos = [...head, newTodoObj, ...tail];
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.