一个节点不能在状态树中存在两次(mobx-state-tree)

A node cannot exists twice in the state tree ( mobx-state-tree )

在以下模型中通过 setSelectedItem 操作为 selectedItem 赋值时,我收到一条错误消息,显示 Error: [mobx-state-tree] A node cannot exists twice in the state tree. Failed to add SearchModel@/results/0 to path '/selectedItem'。我检查了文档,但不确定是什么导致了这个问题。

感谢任何帮助。谢谢!

const SearchModel = types
  .model({
    results: types.array(ItemModel, []),
    selectedItem:types.maybeNull(ItemModel,{ id: 0 })
  })
  .actions(self => ({   
    setSelectedItem(selItem) {
      console.log( 'typeof(selItem)', typeof(selItem));
      self.selectedItem=selItem;
    }
  }));

export default SearchModel;

对于将来寻找此类错误解决方案的任何人,我使用展开运算符将 selItem 的浅表副本分配给 self.selectedItem,问题就消失了。

代码必须如下所示:

const SearchModel = types
  .model({
    results: types.array(ItemModel, []),
    selectedItem:types.maybeNull(ItemModel,{ id: 0 })
  })
  .actions(self => ({   
    setSelectedItem(selItem) {
      self.selectedItem = { ...selItem };
    }
  }));

export default SearchModel;

另一种解决方案是使用 Lodash 库中的 _.deepCopy。它比扩展运算符更通用,因为它会递归地沿着整棵树而不是一层向下移动。这对于较大的树很有用,因此您不必双倍、三倍或四倍传播,并且有 hard-to-read 代码。

这就是您在简单的 mobx-state-tree 商店中使用它的方式。它非常优雅,易于使用。

注意:这是一个递归 pass-by-copy 函数,因此如果对象太大,性能可能会很差。

import _ from 'lodash';
import { types, getRoot, destroy, flow } from "mobx-state-tree";
            
const SearchModel = types
  .model({
    results: types.array(ItemModel, []),
    selectedItem:types.maybeNull(ItemModel,{ id: 0 })
  })
  .actions(self => ({   
    setSelectedItem(selItem) {
      self.selectedItem = _.deepCopy(selItem);
    }
  }));

export default SearchModel;

一个更好的可能性:使用 mobx-state-tree applySnapshot 函数。这应该会自动协调数据结构,并保留 back/forth 能力,因此您可以利用 undo/redo 作为您的状态树。

import { types, getRoot, destroy, flow, applySnapshot } from "mobx-state-tree";
            
const SearchModel = types
  .model({
    results: types.array(ItemModel, []),
    selectedItem:types.maybeNull(ItemModel,{ id: 0 })
  })
  .actions(self => ({   
    setSelectedItem(selItem) {
      applySnapshot(self.selectedItem, selItem);
    }
  }));

export default SearchModel;

在我的例子中,我在第 2 层有一个嵌套对象 "address",所以我也需要为它指定修复:

setSelectedItem(selItem) {
  let item = { ...selItem };
  item.address = { ...selItem.address }
  self.selectedItem = item;
}

我在 React TypeScript 以及 MST 商店中也遇到了同样的问题 非常嵌套且复杂,因此使用展开运算符或 applySnapshot 无效。通过使用 lodash 简单地解决了这个问题。

步骤:

安装: npm i lodash.clonedeep

进口: import cloneDeep from 'lodash/cloneDeep'

使用它(在我的例子中): self.filteredCashGamesList.cashGameTableView = cloneDeep(self.cashGameTableView)