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

это пример моего состояния

{
 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 теперь текст, который мне нужен для обновления.

Ilja

Ответов: 11

Ответы (11)

Вы можете использовать помощники 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}
      }
    }
  });

Вы можете использовать карту. Вот пример реализации:

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

Обратите внимание на структуру данных: в проекте у меня есть такие данные состояние: {комментарии: {items: [{...}, {...}, {...}, ...]} и для обновления одного элемент в элементы Я делаю это

case actionTypes.UPDATE_COMMENT:
  const indexComment = state.comments.items.findIndex( 
    (comment) => comment.id === action.payload.data.id,
  );
  return {
    ...state,
    comments: {
      ...state.comments,
      items: state.comments.items.map((el, index) =>
        index === indexComment ? { ...el, ...action.payload.data } : el,
      ),
    },
  };

Необязательно делать все в одной строке:

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

В моем случае я сделал что-то вроде этого, основываясь на ответе Луиса:

// ...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,
  },
};

Я считаю, что когда вам нужны такие операции с вашим состоянием 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
      }
    }
  }

Используя оператор распространения, вы можете обновить только новое состояние, оставив все остальное нетронутым.

Пример взят из этой замечательной статьи, вы можете найти почти все возможные варианты с отличными примерами.

Боюсь, что использование метода массива 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];
}

Immer.js (замечательный пакет, дружественный к response / rn / redux) решает эту проблему очень эффективно. Хранилище redux состоит из неизменяемых данных - immer позволяет вам обновлять сохраненные данные чисто кодируя, как если бы данные не были неизменяемыми.

Вот пример из их документации для redux: (Обратите внимание на метод Produce (), обернутый вокруг метода. Это единственное изменение в вашей настройке редуктора.)

import produce from "immer"

// Reducer with initial state
const INITIAL_STATE = [
    /* bunch of todos */
]

const todosReducer = produce((draft, action) => {
    switch (action.type) {
        case "toggle":
            const todo = draft.find(todo => todo.id === action.id)
            todo.done = !todo.done
            break
        case "add":
            draft.push({
                id: action.id,
                title: "A new todo",
                done: false
            })
            break
        default:
            break
    }
})

(Кто-то еще упомянул immer как побочный эффект redux-toolkit, но вы должны использовать immer непосредственно в своем редукторе.)

Погружная установка: https://immerjs.github.io/immer/installation

• 100001
// похоже, что состояние изменено, но под капотом Immer отслеживает
// каждое изменение и создание нового состояния для вас
state.x = newValue;

Так что вместо использования оператора распространения в обычном редукторе

return {
  ...государство,
  содержимое: state.contents.map (
      (содержание, я) => я === 1? {... контент, текст: action.payload}
                              : содержание
  )
}

Вы можете просто переназначить локальное значение и позволить Immer сделать все остальное за вас:

state.contents [1] .text = action.payload;

Живая демонстрация

Edit 35628774/how-to-update-single-value-inside-specific-array-item-in-redux

Вот как я это сделал для одного из своих проектов:

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

Очень поздно для вечеринки, но вот общее решение, которое работает со всеми значениями индекса.

  1. Вы создаете и распространяете новый массив из старого массива до индекса, который вы хотите изменить.

  2. Добавьте нужные данные.

  3. Создайте и распределите новый массив из индекса, который вы хотите изменить, до конца массива

let index = 1; // возможно action.payload.id
case 'SOME_ACTION':
   возвращение {
       ...государство,
       содержание: [
          ... state.contents.slice (0, индекс),
          {title: "другой заголовок", text: "другой текст"},
         ... state.contents.slice (индекс + 1)
         ]
    }

Обновление:

Я сделал небольшой модуль для упрощения кода, поэтому вам просто нужно вызвать функцию:

case 'НЕКОТОРЫЕ_ДЕЙСТВИЕ':
   возвращение {
       ...государство,
       содержимое: insertIntoArray (state.contents, index, {title: "некоторый заголовок", text: "некоторый текст"})
    }

Дополнительные примеры см. В репозитории

сигнатура функции:

insertIntoArray (originalArray, insertIndex, newData)

Изменить: Также существует библиотека Immer.js, которая работает со всеми видами значений, и они также могут быть глубоко вложенными.

2022 WebDevInsider