【问题标题】:How to delete/remove an item from normalized state?如何从规范化状态删除/移除项目?
【发布时间】:2019-11-11 03:02:24
【问题描述】:

我有以下状态结构:

{ entities:
1: {name: "Basketball", id: "1", leagues: Array(3)}
2: {name: "Volleyball", id: "2", leagues: Array(3)}
3: {name: "Soccer", id: "3", leagues: Array(0)}
}

现在我只想删除一个 ID 为“3”的项目。

以下不起作用:

const state = ctx.getState();
    delete state.entities[action.id];

    ctx.setState(
      patch<SportTypeStateModel>({
        entities: {...state.entities},
        IDs: state.IDs.filter(id => id !== action.id)
      })
    );

它会抛出以下错误:

`ERROR TypeError: Cannot delete property '3' of [object Object]`

这样做的正确方法是什么?

【问题讨论】:

  • 您的结构似乎缺少括号。什么是“实体”?

标签: angular typescript redux ngxs


【解决方案1】:

首先我们需要了解为什么会出现这个错误。

NGXS 在开发模式的底层使用 deepFreeze Object.freeze 您的状态(以及深度嵌套的对象/数组)来防止不可预知的突变。

您可以拨打Object.isFrozen查看:

const state = ctx.getState();
console.log(Object.isFrozen(state.entities));
delete state.entities[action.id];

我理解你的意思,entities 不是一个数组,而是一个对象。

所以问题是一旦对象被冻结,就无法解冻它。我们需要做什么?我们必须解冻状态对象本身、entities 对象及其子对象:

const state = ctx.getState();
const newState = { ...state, entities: { ...state.entities }};
for (const key of Object.keys(newState.entities)) {
  newState.entities[key] = { ...newState.entities[key] };
}
console.log(Object.isFrozen(newState.entities));
delete newState.entities[action.id];

我不喜欢这段代码,所以不要向我扔石头 :) 我认为您可以搜索一些像 deep-unfreeze 这样的包以更具声明性。哦,我忘了IDs 属性。最终代码为:

ctx.setState(state => {
  const newState = {
    entities: { ...state.entities },
    IDs: state.IDs.filter(id => id !== action.id)
  };
  for (const key of Object.keys(newState.entities)) {
    newState.entities[key] = { ...newState.entities[key] };
  }
  delete newState.entities[action.id];
  return newState;
});

附:在本地检查。

【讨论】:

  • 我认为这正是我需要做的。我很惊讶没有更多关于如何使用规范化实体对象执行此操作的信息。痛苦是因为当单个实体对象包含数组属性时,例如 Leagues: [] 并且每个 League 对象都包含 teams[] 属性。我可以在那里使用 lodash cloneDeep() 来复制所有内容,但不确定我是否应该这样做,特别是因为我有三个用于运动类型、联赛和球队的商店,并且每个实体都有对父级的引用属性。很好的解释!
  • @O.MeeKoh 我是否需要更新我的答案并对您需要的某些部分提供更多解释,因此可以接受这个答案?
  • 那太好了!
【解决方案2】:

您可以过滤使用而不是删除;

newEntities = state.entities.filter(item => item.id !== action.id);

ctx.setState(
      patch<SportTypeStateModel>({
        entities: {...newEntities },
        IDs: state.IDs.filter(id => id !== action.id)
      })
    );

【讨论】:

  • 它不是一个数组。它是一个对象
  • @Poldo 我认为应该是state.entities.filter((item) =&gt; item.id !== action.id); 并且同样正确IDs: state.IDs.filter(id =&gt; id !== action.id)
【解决方案3】:

最简单的方法就是过滤现有状态和补丁。

const state = ctx.getState();
ctx.patchState({
  entities: [...state.entities.filter(e => e.id !== action.id)],
  IDs: [...state.IDs.filter(i => i !== action.id)]
}

您使用的状态模型未在此处列出 - 但如果您要存储实体,将 IDs 属性建模为 @Selector 而不是状态的一部分会更简洁,因为它只是实体列表中内容的投影,例如

@Selector()
static IDs(state: YourStateModel) {
  return state.entities.map(e => e.id);
}

这意味着它始终基于当前的state.entites 值,您不需要维护两个列表。

【讨论】:

    猜你喜欢
    • 2018-05-02
    • 2017-02-24
    • 2016-07-19
    • 2016-10-13
    • 2020-12-15
    • 1970-01-01
    相关资源
    最近更新 更多