Як оновити єдине значення всередині певного елемента масиву в наддуві


106

У мене виникла проблема, коли повторне відображення стану спричиняє проблеми з інтерфейсом, і мені було запропоновано оновити лише певне значення всередині мого редуктора, щоб зменшити кількість рендеринга на сторінці.

це приклад моєї держави

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

Дійсно, чи потрібно мені включати цих помічників, щоб якось реагувати всередині мого редуктора?
Ілля

8
Хоча пакет перейшов на kolodny/immutability-helper, тепер він є yarn add immutability-helperіimport update from 'immutability-helper';
SacWebDeveloper

Мій випадок точно такий же, але коли я оновлюю об'єкт в одному з елементів масиву, оновлене значення не відображається в компонентіWillReceiveProps. Я використовую вміст безпосередньо у своїй картіStateToProps, чи потрібно використовувати щось інше у mapStateToProps, щоб воно відображало?
Шрішті Гупта

171

Можна використовувати 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
Я думаю, що це найкращий підхід
Ісус Гомес

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, чи масив фіксованої довжини? і чи є індекс, який ви бажаєте змінити, фіксованим? Поточний підхід є життєздатним лише з малим масивом та з фіксованим індексом.
Юя

2
загортайте корпус у {}, оскільки const є блоком дії.
Бенні Пауерс

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

Використовуючи оператор розповсюдження, ви можете оновити лише новий стан, залишивши все іншим недоторканим.

У прикладі, взятому з цієї дивовижної статті , ви можете знайти майже кожен можливий варіант із чудовими прикладами.


3

У моєму випадку я зробив щось подібне, грунтуючись на відповіді Луїса:

...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 - предмети перед зміненим елементом
  • модифікований елемент
  • хвіст - елементи після зміненого елемента

Ось приклад, який я використовував у своєму коді (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];
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.