Javascript 如何在redux中更新特定数组项中的单个值

Javascript 如何在redux中更新特定数组项中的单个值,javascript,reactjs,redux,Javascript,Reactjs,Redux,我有一个问题,状态的重新呈现会导致ui问题,建议只更新我的reducer中的特定值,以减少页面上的重新呈现量 这是我的国家的例子 { name: "some name", subtitle: "some subtitle", contents: [ {title: "some title", text: "some text"}, {title: "some other title", text: "some other text"} ] } 我现在正在像这样更新它 cas

我有一个问题,状态的重新呈现会导致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
是包含新值的整个数组。但现在我实际上只需要更新contents数组中第二项的文本,这样的东西不起作用

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

其中
action.payload
现在是我需要更新的文本。

您不必在一行中完成所有操作:

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

虽然我想你可能会做更多类似的事情

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

您可以使用
map
。下面是一个示例实现:

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

派对已经很晚了,但这里有一个通用的解决方案,可以处理每个索引值

  • 您可以创建一个新数组,并将其从旧数组扩展到要更改的
    索引

  • 添加所需的数据

  • 从要更改的
    索引
    创建并展开一个新数组,直到数组末尾

  • let index=1;//可能是action.payload.id
    案例“某些行动”:
    返回{
    ……国家,
    内容:[
    …state.contents.slice(0,索引),
    {标题:“其他一些标题”,文本:“其他一些文本”},
    …state.contents.slice(索引+1)
    ]
    }
    
    更新:

    我制作了一个小模块来简化代码,因此您只需调用一个函数:

    案例“某些行动”:
    返回{
    ……国家,
    内容:insertIntoArray(state.contents,index,{title:“some title”,text:“some text”})
    }
    
    有关更多示例,请查看

    功能签名:

    插入数组(原始数组、插入索引、新数据)
    
    编辑:
    还有一个库可以处理所有类型的值,它们也可以进行深度嵌套。

    我相信当您在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
          }
        }
      }
    
    通过使用spread操作符,您可以只更新新状态,而保留所有其他内容


    从这个例子中,你可以找到几乎每一个可能的选择和伟大的例子。

    在我的例子中,我做了这样的事情,基于路易斯的回答:

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

    这是我为我的一个项目所做的:

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

    我担心使用数组的
    map()。相反,我合并了一个由三部分组成的新数组:

    • 标题-修改项目之前的项目
    • 修改后的项目
    • 尾部-修改项目后的项目
    这里是我在代码中使用的示例(NgRx,但机制与其他Redux实现相同):


    这在redux工具包中非常简单,它使用Immer帮助您编写看起来像可变的、更简洁、更易于阅读的不可变代码

    //看起来状态发生了变化,但在幕后伊默一直在跟踪
    //每一次变化都会为你创造一个新的状态
    state.x=新值;
    
    因此,不必在普通redux减速机中使用spread运算符

    返回{
    ……国家,
    目录:state.contents.map(
    (content,i)=>i==1?{…content,text:action.payload}
    :内容
    )
    }
    
    您只需重新分配局部值,然后让Immer为您处理其余的值:

    state.contents[1]。text=action.payload;
    
    现场演示

    你说得对,我做了一个快速修复。顺便问一下,数组的长度是固定的吗?您希望修改的索引是否也已修复?当前的方法仅适用于小数组和固定索引。将您的案例主体包装在{}中,因为const是块范围的。确实,我是否需要以某种方式将这些helper从react包含在我的reducer中?尽管包已移动到
    kolodny/immutability helper
    ,但是,现在是添加不变性助手和从“不变性助手”导入更新我的情况完全相同,但当我更新数组中某个元素中的对象时,更新的值不会反映在componentWillReceiveProps中。我直接在MapStateTrops中使用内容,我们是否必须在MapStateTrops中使用其他内容来反映它?我也是这样做的,我只是不确定这是否是一种好方法,因为map将返回一个新数组。有什么想法吗?@Ventura是的,这很好,因为它为数组创建了一个新的引用,为要更新的数组创建了一个简单的新对象(并且只有该对象),这正是您想要的。我认为这是最好的方法。请经常这样做,但是迭代所有元素而不是更新必要的索引在计算上似乎相对昂贵,这与内存成本无关。这是关于不让你的应用程序陷入指数循环的问题。在我看来,当OP想要更新ArrayEp时,这就像一个对象,并没有真正回答问题
    findIndex
    也会迭代数组。在最坏的情况下(元素在末尾),它迭代整个数组。然后你进行迭代来创建头部和尾部数组(我相信切片就是这样工作的),然后再次迭代来将它们传播到newTodos中。我还认为有一个bug,因为
    slice
    中的
    end
    参数是独占的,通过传递
    todoIdx-1
    会丢失一个元素。
    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;
      }
    };
    
    // 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];
    }