Angular和NGRX防止选择器在值相等时在状态更改时发出相同的值

Chr*_*ris 5 ngrx angular

我正在寻找一种解决方案,以使选择器仅在与上次发出的值相比发生变化时才发出新值,并且不仅更改对存储的引用。

我的商店中处于以下状态:

{
   items: [],
   loading: false,
   selectedItemId: 1
}
Run Code Online (Sandbox Code Playgroud)

我有以下选择器:

export const getSelectedItem = createSelector(getItemsState,
    (state) => {
        return state.selectedItemId === null ? null : state.items.find(item => item.id === state.selectedItemId)
    }
);
Run Code Online (Sandbox Code Playgroud)

当我对此选择器进行订阅时,每次例如在商店中的加载标志更改时,都会触发该事件。我希望选择器仅在所选项目的值更改时发出一个值。即从1到2。但是当状态对象的引用不同时则不是。

我为此找到了一个解决方案:

this.itemStore.select(getSelectedItem).distinctUntilChanged((x, y) => {
    return x.id === y.id;
}).subscribe(item => {
    // do something
});
Run Code Online (Sandbox Code Playgroud)

但是,我想移动逻辑以使更新的内容与我的选择器区分开,而不是在调用方使用。

我理解为什么会有这种行为,因为框架每次都检查简化时是否检查对象是否相等,并且商店引用发生了变化,因为我们每次都会从化简器中返回一个新的ItemState对象。但是我无法找到解决问题的方法,而且我无法想象我是唯一需要选择器的人,该选择器仅在更改了有效值后才会更新。

减速器的代码如下所示:

export function itemsReducer(state: ItemsState = initialItemsState, action: Action): ItemsState {
    switch(action.type) {
        case itemActions.ITEMS_LOAD:
            return {
                ...state,
                itemsLoading: true
            };
        case itemActions.ITEMS_LOAD_SUCESS:
            return {
                ...state,
                items: action.payload,
                itemsLoading: false
            };
        // ... a lot of other similar actions
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*hes 5

使用您当前的减速器,distinctUntilChanged是处理此问题的正确操作员。解决此问题的另一种方法是让您的减速器在更新项目时检测对象是否“功能不变”,例如:

export function itemsReducer(state: ItemsState = initialItemsState, action: Action): ItemsState {
    switch(action.type) {
        case itemActions.ITEMS_LOAD:
            return {
                ...state,
                itemsLoading: true
            };
        case itemActions.ITEMS_LOAD_SUCESS:
            return {
                ...state,
                items: action.payload.map((newItem) => {
                   let oldItem = state.items.find((i) => i.id == newItem.id);
                   // If the item exists in the old state, and is deeply-equal
                   // to the new item, return the old item so things watching it
                   // know they won't need to update.
                   if (oldItem && itemEqual(newItem, oldItem)) return oldItem;

                   // Otherwise, return the newItem as either there was no old
                   // item, or the newItem is materially different to the old.
                   return newItem;
                }),
                itemsLoading: false
            };
        // ... a lot of other similar actions
    }
}
Run Code Online (Sandbox Code Playgroud)

您需要以某种方式实现 itemEqual 以检查深层逻辑相等性。在这里,您可以使用您对功能上“相等”意味着什么的知识来确定项目是否已更改,例如:

function itemEqual(item1, item2) {
    // Need here to do a /deep/ equality check between 
    // the two items, e.g.:
    return (item1.id == item2.id && 
           item1.propertyA == item2.propertyA &&
           item1.propertyB == item2.propertyB)
}
Run Code Online (Sandbox Code Playgroud)

您可以使用类似 lodash 的_.isEqual()函数而不是实现自己的函数来itemEqual对对象进行一般的深度相等检查。