两个不可变列表 - 如何使三重相等起作用?

Posted

技术标签:

【中文标题】两个不可变列表 - 如何使三重相等起作用?【英文标题】:Two immutable lists - how to make triple equality work? 【发布时间】:2016-05-02 21:35:50 【问题描述】:

假设我们有一个使用 Facebook 的 Immutable.js 创建的不可变对象。我想比较使用.map.filter 从单一来源生成的两个列表,并确保它们相等。在我看来,当使用 map/filter 时,您正在创建一个与先前对象无关的新对象。如何使三重相等=== 起作用?这有意义吗?

var list = Immutable.List([ 1, 2, 3 ]);
var list1 = list.map(function(item)  return item; )
var list2 = list.map(function(item)  return item; )

console.log("LIST1 =", list1.toJS())      // [1, 2, 3]
console.log("LIST2 =", list2.toJS())      // [1, 2, 3]
console.log("EQUAL ===?", list1===list2); // false! Why? How?

你可以在这里玩:http://jsfiddle.net/eo4v1npf/1/

上下文

我正在使用 React + Redux 构建应用程序。我的州有一个列表,其中包含具有selected 属性的项目:

items: [
    id: 1, selected: true,
    id: 2, selected: false,
    id: 3, selected: false,
    id: 4, selected: true
]

我只想将 选定的 ids 传递给另一个容器,所以我尝试使用简单的connect

function getSelectedIds(items) 
    return items
        .filter((item) => item.get("selected"))
        .map((item) =>  item.get("id"));


export default connect(
    (state: any) => (
        ids: getSelectedIds(state.get("items"))
)
)(SomePlainComponent);

问题是,如果我设置其他属性:

id: 1, selected: true, note: "The force is strong with this one"

这会导致状态发生变化,SomePlainComponent 会重新呈现,尽管所选 Id 的列表完全相同相同。如何确保纯渲染器正常工作?

编辑一些附加信息

为了反应纯渲染,我使用了来自 react-pure-render 的 mixin:

export default function shouldPureComponentUpdate(nextProps, nextState) 
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState);

由于它不知道可能是不可变的道具,因此它们被视为已更改,即

this.props = 
    ids: ImmutableList1


nextProps = 
    ids: ImmutableList2

虽然ids 两个属性在内容上是相等的,但它们是完全不同的对象,没有通过ImmutableList1 === ImmutableList2 测试,shouldComponentUpdate 返回true。 @Gavriel 正确地指出深度平等会有所帮助,但这应该是最后的手段。

无论如何,我会应用接受的解决方案,问题就会得到解决,谢谢大家! ;)

【问题讨论】:

为什么不使用 deepEqual:***.com/questions/25456013/… 你确定它会重新渲染吗? React 的工作方式是,当状态中的一个项目发生变化时,整个新状态与整个旧状态进行比较。因此,状态已经循环,如果您在该生命周期内记录某些内容,您可能会收到一些日志。但是,如果 DOM 相关的内容没有发生任何变化,那么 react 很可能不会重新渲染任何内容。检查 shouldComponentUpdate 方法。 因为数据是不可变的.. 【参考方案1】:

由于 Immutable.js 对象本质上是唯一的,因此您永远不能严格相等的不可变结构。

您可以使用.is 函数,该函数接受两个不可变对象并比较其中的值。这是因为不可变结构实现了equalshashCode

var map1 = Immutable.Map(a:1, b:1, c:1);
var map2 = Immutable.Map(a:1, b:1, c:1);
console.log(Immutable.is(map1, map2));
// true

【讨论】:

【参考方案2】:

如果您想保持组件纯净并使用===,那么您还可以对 Redux 状态进行非规范化并将 selectedIds 作为属性存储在存储中。仅当发生添加/删除所选项目或切换项目选择的操作时更新此列表,而不是在更新项目的其他任意属性时更新此列表。

【讨论】:

这听起来是个好主意,但是我的状态将存储在两个地方,我必须使用...操作来同步它?状态看起来像: selectedItems: [1], items: [ id: 1, selected: true, id: 2, selected: false ] 在处理选择操作时,我需要更新两个状态属性,这听起来很容易,但我必须一直这样做:当我删除一个项目,当我切换到下一页等。这会导致代码周围有很多额外的逻辑:) 确实,如果操作导致选择发生变化,那么当前修改items 属性的每个reducer 也需要修改selectedItems 属性。大概您只有少数几个受到影响的操作?第二种选择是从项目中删除选定的属性,并将选定的项目作为单独的属性存储在商店中,作为列表或作为集合:@987654325 @ 或 selectedIdSet: Set(1,...), items:id:1,name:"foo",...]

以上是关于两个不可变列表 - 如何使三重相等起作用?的主要内容,如果未能解决你的问题,请参考以下文章

比较字典与不可用或不可比的值? (例如列表或数据帧)

将可变参数列表传递给 syslog

可变类型与不可变类型的作用和意义

有没有办法使不可变引用可变?

如何使以下类不可变?

如何使类属性不可变?