这是使用redux删除项目的正确方式吗?

143

我知道我不应该改变输入并应该克隆对象来进行修改。我遵循了一个redux入门项目中使用的惯例:

ADD_ITEM: (state, action) => ({
  ...state,
  items: [...state.items, action.payload.value],
  lastUpdated: action.payload.date
})

对于添加一个项目-我理解使用展开运算符将项目附加到数组中。

对于删除,我使用了:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: [...state.items.splice(0, action.payload), ...state.items.splice(1)],
  lastUpdated: Date.now() 
})

但这会改变输入的状态对象——即使我返回一个新对象,这也是被禁止的吗?


1
快速提问。Splice返回您删除的项目。这是您的意图吗?如果不是,您应该使用slice,它也遵守无突变法则。 - m0meni
在这个例子中,我将数组的两个部分拼接成一个新数组 - 并且排除了我想要删除的项。Slice 也会返回被删除的项,对吧?只是它不会改变原始数组,所以这应该是更好的方法? - CWright
根据@AR7的建议: 现在使用slice而不是splice来避免改变输入,代码如下: items: [...state.items.slice(0, action.payload.value), ...state.items.slice(action.payload.value + 1 )] 这样做是否正确?或者有更简洁的方法吗? - CWright
7个回答

240

不要改变状态。即使你返回一个新对象,你仍然会污染旧对象,这是绝不想要的。这使得在旧状态和新状态之间进行比较时出现问题。例如在react-redux底层使用的shouldComponentUpdate中。它还使时间旅行成为不可能(即撤消和重做)。

相反,请使用不可变方法。始终使用Array#slice而不是Array#splice

从你的代码中我推断action.payload是要删除的项目的索引。更好的方法如下:

items: [
    ...state.items.slice(0, action.payload),
    ...state.items.slice(action.payload + 1)
],

如果我们正在处理数组中的最后一个元素,则此方法无效,而且在第二个语句中使用 ... 将会使您的状态内容翻倍。 - Thaenor
4
请在 jsfiddle/codepen 上用示例证明:无论 arr 的内容是什么,代码片段 arr.slice(arr.length) 都应始终生成一个空数组。 - David L. Walsh
1
@david-l-walsh 对于混淆感到抱歉,我在测试这个例子时可能打错了字或者做了什么。它的效果非常好。我唯一的问题是:为什么在第二部分需要使用扩展运算符 ... - ...state.items.slice(action.payload + 1) - Thaenor
7
Array#slice 返回一个数组。为了将两个片段合并成一个数组,我使用了扩展运算符。如果没有它,你将得到一个由多个数组组成的数组。 - David L. Walsh
5
有道理。非常感谢您的澄清(一开始让您困惑了,很抱歉)。 - Thaenor

191
您可以使用数组过滤方法从数组中删除特定元素,而不会改变原始状态。
return state.filter(element => element !== action.payload);

在您的代码环境中,它会看起来像这样:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => item !== action.payload),
  lastUpdated: Date.now() 
})

1
过滤器是否会生成一个新的数组? - chenop
7
是的,Array.filter方法会返回一个新数组。详见https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter。 - Steph M
10
请注意,如果有重复项,这将删除所有重复项。如果要使用筛选器删除特定索引,可以使用例如arr.filter((val, i) => i !== action.payload ) - Eric Haynes

27

ES6的Array.prototype.filter方法会返回一个包含符合条件元素的新数组。因此,在原问题的上下文中,答案为:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => action.payload !== item),
  lastUpdated: Date.now() 
})

.filter()不是ES2015的方法,而是在之前的版本ES5中添加的。 - morkro

12

另一种不可变的“DELETED”数组对象reducer变体:

const index = state.map(item => item.name).indexOf(action.name);
const stateTemp = [
  ...state.slice(0, index),
  ...state.slice(index + 1)
];
return stateTemp;

2

使用不同方式在redux中删除项目。

方法1:在这种情况下使用createSlice(..)

const { id } = action.payload; // destruct id
removeCart: (state, action) =>{
                 let { id } = action.payload;
                 let arr = state.carts.filter(item => item.id !== parseInt(id))
                 state.carts = arr;
               }

方法二:使用 switch (... ),扩展运算符。
const { id } = action.payload; // destruct id

case actionTypes.DELETE_CART:  
     return {
        ...state,
        carts: state.carts.filter((item) => item.id !== payload)
      };

对于这两种方法,都会初始化此状态:

  initialState: {
      carts: ProductData, // in productData mocked somedata     
    }

0

黄金法则是我们不返回突变的状态,而是返回一个新的状态。根据您的操作类型,当它到达 reducer 时,您可能需要以各种形式更新状态树。

在这种情况下,我们正在尝试从状态属性中删除一个项目。

这就引出了 Redux 不可变更新(或数据修改)模式的概念。不可变性很重要,因为我们永远不希望直接更改状态树中的值,而是始终复制并基于旧值返回新值。

以下是如何删除嵌套对象的示例:

// ducks/outfits (Parent)

// types
export const NAME = `@outfitsData`;
export const REMOVE_FILTER = `${NAME}/REMOVE_FILTER`;

// initialization
const initialState = {
  isInitiallyLoaded: false,
  outfits: ['Outfit.1', 'Outfit.2'],
  filters: {
    brand: [],
    colour: [],
  },
  error: '',
};

// action creators
export function removeFilter({ field, index }) {
  return {
    type: REMOVE_FILTER,
    field,
    index,
  };
}

export default function reducer(state = initialState, action = {}) {
  sswitch (action.type) {  
  case REMOVE_FILTER:
  return {
    ...state,
    filters: {
    ...state.filters,
       [action.field]: [...state.filters[action.field]]
       .filter((x, index) => index !== action.index)
    },
  };
  default:
     return state;
  }
}

为了更好地理解这个问题,请确保查看这篇文章:https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da

0
从数组中不可变/功能性地删除索引处的元素:
items: [
    ...items.slice(0, index),
    ...items.slice(index + 1)
]

或者更简单(可能也更快):
items: items.filter((_, i) => i !== index)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接