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

时间:2018-08-22 07:05:32

标签: angular ngrx

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

我的商店中有以下状态:

{
   items: [],
   loading: false,
   selectedItemId: 1
}

我有以下选择器:

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

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

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

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

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

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

reducer的代码如下:

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
    }
}

3 个答案:

答案 0 :(得分:3)

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

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
    }
}

您将需要以某种方式实现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)
}

您可以使用lodash的_.isEqual()函数之类的东西来代替实现自己的itemEqual来对对象进行一般的深度相等检查。

答案 1 :(得分:1)

在减速器中添加一个检查当前状态的检查。和有效载荷。如果有效负载值与您当前的状态相同,请不要覆盖该状态。但按原样返回当前状态。并且只有在值和引用确实发生更改的情况下,减速器才会启动。

like:

case 'XXXSTATE':  
if(state.xxx.value === payload.value)
       return state; 
else 
       return {...state,itemsLoading: true};

也redux不仅检查引用,但这是另一个主题:)

答案 2 :(得分:0)

代码的问题是您要返回相同的值。 要触发更改,您必须返回一个新对象,而不是对现有值的引用。

webapps