redux에서 특정 배열 항목 내부의 단일 값을 업데이트하는 방법


106

상태를 다시 렌더링하면 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지금 업데이트가 필요한 텍스트는 어디에 있습니까 ?

답변:


58

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-helperimport update from 'immutability-helper';
SacWebDeveloper

내 경우는 정확히 동일하지만 배열의 요소 중 하나에서 개체를 업데이트 할 때 업데이트 된 값이 componentWillReceiveProps에 반영되지 않습니다. 내 mapStateToProps에서 직접 콘텐츠를 사용합니다. 반영하기 위해 mapStateToProps에서 다른 것을 사용해야합니까?
Srishti Gupta

171

사용할 수 있습니다 map. 다음은 구현의 예입니다.

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

그것이 내가하는 방법이기도합니다.지도가 새로운 배열을 반환하기 때문에 그것이 좋은 접근인지 아닌지 확실하지 않습니다. 이견있는 사람?
Thiago C. S Ventura 16.11.

@Ventura 예, 배열에 대한 새 참조를 만들고 업데이트 할 대상 (그리고 그 하나만)에 대한 일반 새 개체를 만들고 있기 때문에 좋습니다. 정확히 원하는 것입니다
ivcandela

3
나는이 가장 aproach 생각
예수님 고메즈

12
이 작업도 자주 수행하지만 필요한 인덱스를 업데이트하는 것보다 모든 요소를 ​​반복하는 것이 상대적으로 계산 비용이 많이 드는 것 같습니다.
Ben Creasy

예쁜! 나는 그것을 사랑한다!
Nate Ben

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
        }
    }
}

그리고 Ravenclaw에 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
      }
    }
  }

스프레드 연산자를 사용하면 나머지는 그대로두고 새 상태 만 업데이트 할 수 있습니다.

놀라운 기사 에서 가져온 예제 , 훌륭한 예제와 함께 가능한 거의 모든 옵션을 찾을 수 있습니다.


3

제 경우에는 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()전체 배열이 반복되기 때문에 배열 방법 을 사용 하는 것이 비용이 많이들 수 있습니다. 대신 세 부분으로 구성된 새 배열을 결합합니다.

  • head- 수정 된 항목 이전의 항목
  • 수정 된 항목
  • tail- 수정 된 항목 이후의 항목

다음은 내 코드에서 사용한 예제입니다 (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.