ngrx - createSelector vs Observable.combineLatest

时间:2018-01-10 12:25:59

标签: angular redux observable ngrx combinelatest

我刚刚遇到@ngrx的{​​{3}},我根本无法对此功能感到惊讶。

根据books的{​​{1}}用例,我无法给出使用自定义选择器的真正理由,例如:

selectedUser

而不是像:

export const selectVisibleBooks = createSelector(selectUser, selectAllBooks, (selectedUser: User, allBooks: Books[]) => {
    return allBooks.filter((book: Book) => book.userId === selectedUser.id);
});

我试图说服自己export const selectVisibleBooks = Observable.combineLatest(selectUser, selectAllBooks, (selectedUser: User, allBooks: Books[]) => { return allBooks.filter((book: Book) => book.userId === selectedUser.id); }); 的{​​{3}}是关键部分,但据我所知,它不能对非原始值执行这些性能提升,所以它不会真的保存非原始切片的任何计算,使用createSelector的{​​{3}}运算符和Rx可以解决。

那么我错过了什么,为什么要使用combineLatest

提前感谢任何见解。

1 个答案:

答案 0 :(得分:3)

也许除了记忆之外还有更多内容,但我没有看到source code中突出的任何内容。在docs中公布的所有内容都是memoization和重置它的方法,您基本上可以使用不同的运算符。我说使用它的原因是它很方便。至少在简单的情况下,将不同的运算符绑定到combineLatest的每个输入都比这更方便。

另一个好处是它允许您集中与您所在州的内部结构相关的逻辑。您可以为其创建选择器并执行store.select(x => foo.bar.baz),而不是在任何地方执行store.select(selectBaz)。您可以将选择器组合到。通过这种方式,您只需要设置逻辑以在一个位置遍历状态树。如果您必须更改状态的结构,这是有益的,因为您只需要在一个地方进行更改而不是找到每个选择器。但是,每个人都可能不喜欢创建更多样板文件。但作为一个必须做国家重大改造的人,我只使用选择器。

createSelector非常基本,但您只能将其用于基本类型的操作。在您检索仅需要过滤子集的对象列表的情况下,它不足。这是一个例子:

const selectParentVmById = (id: string) => createSelector<RootState, Parent, Child[], ParentVm>(
    selectParentById(id),
    selectChildren(),
    (parent: Parent, children: Child[]) => (<ParentVm>{
        ...parent,
        children: children.filter(child => parent.children.includes(child.id))
    })
);

在这种情况下,当selectParentVmById发出不同的数组时,选择器selectChildren()将会发出,如果其中的任何元素发生更改,则会发生这种情况。如果改变的元素是父母的孩子之一,这是很好的。如果它不是那么你会得到不必要的流失,因为memoization是在整个列表而不是筛选列表(或者更确切地说是其中的元素)上完成的。我有很多这样的场景,并且只使用createSelector开始用于简单的选择器,并将它们与combineLatest组合并滚动我自己的memoization。

这不是一般不使用它的理由,你只需知道它的局限性。

额外学分

你的问题不是关于这个问题,但是自从我提出问题以来,我认为我已经提出了完整性的解决方案。我开始使用一个名为distinctElements()的自定义运算符,它的行为类似distinctUntilChanged(),但适用于列表中的元素而不是列表本身。

以下是运营商:

import { Observable } from 'rxjs/Observable';
import { startWith, pairwise, filter, map } from 'rxjs/operators';

export const distinctElements = () => <T extends Array<V>, V>(source: Observable<T>) => {
    return source.pipe(
        startWith(<T>null),
        pairwise(),
        filter(([a, b]) => a == null || a.length !== b.length || a.some(x => !b.includes(x))),
        map(([a, b]) => b)
    )
};

以上是重构使用它的代码:

const selectParentVmById = (store: Store<RootState>, id: string): ParentVm => {
    return store.select(selectParentById(id)).pipe(
        distinctUntilChanged(),
        switchMap((parent) => store.select(selectChildren()).pipe(
            map((children) => children.filter(child => parent.children.includes(child.id))),
            distinctElements(),
            map((children) => <ParentVm> { ...parent, children })
        ))
    );
}

需要更多代码,但它会减少浪费的工作。您可以添加shareReplay(1),具体取决于您的方案。