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

Posted

技术标签:

【中文标题】如何在redux中更新特定数组项内的单个值【英文标题】:How to update single value inside specific array item in 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 现在是我需要更新的文本。

【问题讨论】:

【参考方案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】:

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

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

    添加你想要的数据。

    从您要更改的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 是的,这很好,因为它为数组创建了一个新引用,并为打算更新的那个(并且只有那个)创建了一个普通的新对象,这正是你想要的 我认为这是最好的方法 我也经常这样做,但是迭代所有元素而不是更新必要的索引在计算上似乎相对昂贵。 这与内存成本无关。这是关于不通过进入指数循环来减慢您的应用程序

以上是关于如何在redux中更新特定数组项内的单个值的主要内容,如果未能解决你的问题,请参考以下文章

如何在 vue 3 中更新 ref 内的数组的值

当redux store中的值发生变化时,如何更新formik中的特定表单字段?

如何在 MySQL 中的 JSON 数组内的元素中提取特定属性的所有值?

如何在 Mongoose 中更改文档子数组的对象内的布尔值?

mule/java - 从 java 类读取 jar 依赖项内的属性文件

如何在CloudFirestore中更改数组中的对象? [重复]