【问题标题】:ngrx - createSelector vs Observable.combineLatestngrx - createSelector vs Observable.combineLatest
【发布时间】:2018-06-19 15:02:31
【问题描述】:

我刚刚遇到了@ngrxcustom selectors,我根本无法对这个功能感到惊讶。

按照他们对selectedUserbooks 用例,我无法给出使用自定义选择器的真正充分理由,例如:

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

我试图说服自己createSelectormemoization 是关键部分,但据我了解,它无法对非原始值执行这些性能提升,因此它不会真正节省任何计算非原始切片,通过使用RxdistinctUntilChanged运算符和combineLatest可以解决。

那么我错过了什么,我为什么要使用@ngrx/selector

提前感谢您提供任何见解。

【问题讨论】:

  • 就我个人而言,我宁愿坚持尽可能多的纯 rxjs,所以我选择使用 combineLatest 而不是 createSelector,这样我就知道更接近金属的情况了。
  • @born2net 在过去的所有时间之后,我想我喜欢这种方法,就像你说的 - 更多控制

标签: angular redux observable ngrx combinelatest


【解决方案1】:

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

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

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() 发出不同的数组时发出,如果其中的任何元素发生更改,就会发生这种情况。如果更改的元素是父母的孩子之一,那就太好了。如果不是,那么您将获得不必要的流失,因为记忆是在整个列表而不是过滤列表(或者更确切地说是其中的元素)上完成的。我有很多这样的场景,并且开始只将createSelector 用于简单的选择器并将它们与combineLatest 结合起来并滚动我自己的记忆。

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

额外积分

您的问题与此无关,但自从我提出问题后,我想我会给出完整的解决方案。我开始使用一个名为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)

【讨论】:

    猜你喜欢
    • 2018-04-10
    • 1970-01-01
    • 2020-01-28
    • 2021-04-08
    • 1970-01-01
    • 1970-01-01
    • 2023-02-26
    • 2019-05-01
    • 1970-01-01
    相关资源
    最近更新 更多