如何在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()
方法可能会很昂贵,因为要迭代整个数组。相反,我组合了一个由三部分组成的新数组:
这是我在代码中使用的示例(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-helper
和import 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中更新特定数组项内的单个值的主要内容,如果未能解决你的问题,请参考以下文章
当redux store中的值发生变化时,如何更新formik中的特定表单字段?
如何在 MySQL 中的 JSON 数组内的元素中提取特定属性的所有值?
如何在 Mongoose 中更改文档子数组的对象内的布尔值?