如何在Redux中更新特定数组项内的单个值


106

我遇到一个问题,即状态的重新呈现会导致ui问题,并建议仅更新我的reducer内部的特定值以减少页面上的重新呈现量。

这是我的状态的例子

{
 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现在我需要更新的文本在哪里。

Answers:


58

您可以使用React不变性助手

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)2016年

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

这也是我的做法,我只是不确定这是否是一个好方法,因为map将返回一个新数组。有什么想法吗?
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
没错,我做了快速修复。顺便说一句,数组是固定长度的吗?您要修改的索引是否也固定?当前的方法仅适用于小型数组和固定索引。
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
      }
    }
  }

通过使用传播运算符,您可以仅更新新状态,而其他所有内容均保持不变。

摘自这篇精彩文章的示例,您可以找到包含出色示例的几乎所有可能的选项。


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()数组的使用方法可能会很昂贵,因为要迭代整个数组。相反,我合并了一个新的数组,该数组包含三个部分:

  • 修改后的项目之前的项目
  • 修改后的项目
  • 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];
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.