【问题标题】:How to update single value inside specific array item in redux如何在redux中更新特定数组项内的单个值
【发布时间】:2016-06-08 07:32:35
【问题描述】:

我遇到了一个问题,即重新渲染状态会导致 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 现在是我需要更新的文本。

【问题讨论】:

    标签: javascript reactjs redux


    【解决方案1】:

    Immer.js(一个惊人的 react/rn/redux 友好包)非常有效地解决了这个问题。 redux 存储由不可变数据组成 - immer 允许您以干净的编码更新存储的数据,就好像数据不是不可变的一样。

    这是他们的 redux 文档中的示例: (注意该方法周围的 producer() 方法。这确实是减速器设置中唯一的变化。)

    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 的副作用,但你应该直接在你的 reducer 中使用 immer。)

    浸入式安装: https://immerjs.github.io/immer/installation

    【讨论】:

      【解决方案2】:

      注意数据结构: 在一个项目中,我有这样的数据 state:{comments:{items:[{...},{...},{...},...]} 并更新 items 中的一个 item 我这样做

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

      【讨论】:

        【解决方案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,
          },
        };
        
        

        【讨论】:

          【解决方案4】:

          聚会很晚,但这里有一个适用于每个索引值的通用解决方案。

          1. 您创建一个新数组并将其从旧数组传播到您要更改的index

          2. 添加你想要的数据。

          3. 从您要更改的index 创建并传播一个新数组到数组末尾

          let index=1;// probably 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"})
              }
          

          更多示例,请查看repository

          函数签名:

          insertIntoArray(originalArray,insertionIndex,newData)
          

          编辑: 还有Immer.js库,可以处理各种值,也可以深度嵌套。

          【讨论】:

            【解决方案5】:

            这在 redux-toolkit 中非常简单,它使用 Immer 帮助您编写看起来像 mutable 的不可变代码,更简洁易读。

            // it looks like the state is mutated, but under the hood Immer keeps track of
            // every changes and create a new state for you
            state.x = newValue;
            

            所以不必在普通的 redux reducer 中使用扩展运算符

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

            您可以简单地重新分配本地值,让 Immer 为您处理其余的:

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

            现场演示

            【讨论】:

              【解决方案6】:

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

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

              【讨论】:

              • 你说得对,我做了一个快速修复。顺便说一句,数组是固定长度的吗?您希望修改的索引是否也是固定的?目前的方法只适用于小数组和固定索引。
              • 将案例主体包装在 {} 中,因为 const 是块作用域。
              【解决方案7】:

              恐怕使用数组的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];
              }
              

              【讨论】:

              • findIndex 也会迭代数组。在最坏的情况下(元素在末尾)它会迭代整个数组。然后你进行迭代以创建头部和尾部数组(我相信这就是 slice 的工作原理),然后再次迭代以将它们传播到 newTodos 中。我也认为有一个错误,因为slice 中的end 参数是独占的,所以您通过传递todoIdx - 1 会丢失一个元素。
              【解决方案8】:

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

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

              【讨论】:

                【解决方案9】:

                我相信当你需要在你的 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
                      }
                    }
                  }
                

                通过使用扩展运算符,您可以只更新新状态,而其他一切都保持不变。

                来自amazing article 的示例,您可以找到几乎所有可能的选项以及很好的示例。

                【讨论】:

                • 当 OP 想要更新数组时,这对我来说就像一个对象
                • 是的,并没有真正回答问题
                【解决方案10】:

                您可以使用React Immutability helpers

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

                【讨论】:

                • 确实,我是否需要以某种方式在我的减速器中包含这些帮助器?
                • 虽然包已经移到kolodny/immutability-helper,所以现在是yarn add immutability-helperimport update from 'immutability-helper';
                • 我的情况完全一样,但是当我在数组中的一个元素中更新对象时,更新的值不会反映在 componentWillReceiveProps 中。我直接在我的 mapStateToProps 中使用内容,我们是否必须在 mapStateToProps 中使用其他内容才能反映?
                【解决方案11】:

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

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

                【讨论】:

                • 我也是这样做的,我只是不确定这是否是一个好方法,因为 map 会返回一个新数组。有什么想法吗?
                • @Ventura 是的,这很好,因为它为数组创建了一个新引用,并为打算更新的那个(并且只有那个)创建了一个普通的新对象,这正是你想要的
                • 我认为这是最好的方法
                • 我也经常这样做,但是迭代所有元素而不是更新必要的索引在计算上似乎相对昂贵。
                • 这与内存成本无关。这是关于不通过进入指数循环来减慢您的应用程序
                猜你喜欢
                • 2018-08-07
                • 1970-01-01
                • 1970-01-01
                • 2019-01-27
                • 1970-01-01
                • 1970-01-01
                • 2022-01-24
                • 1970-01-01
                • 2021-03-06
                相关资源
                最近更新 更多