我刚刚遇到了@ngrx
的custom selectors,我简直无法对这个特性感到惊讶。
按照books
对于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);
});
我试图说服自己,
createSelector
的memoization是关键部分,但据我所知,它无法对非原始值执行这些性能提升,因此它不会真正为非原始切片节省任何计算,使用Rx
的distinctUntilChanged
运算符和combineLatest
可以解决这一问题。那么,我遗漏了什么,为什么要使用
@ngrx/selector
?提前感谢您的任何见解。
最佳答案
也许这不仅仅是一个记忆,但我没有看到任何突出的source code。在docs中公布的只是备忘录和重置它的方法,基本上也可以使用不同的运算符来完成。我想说使用它的原因是它很方便。至少在简单的情况下,它比将不同的运算符绑定到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()
将发出,如果其中的任何元素发生了变化,就会发生这种情况。如果更改的元素是父元素的子元素之一,则这非常好。如果不是这样,你就会得到不必要的变动,因为备忘录是在整个列表上完成的,而不是过滤列表(或者是其中的元素)。我有很多这样的场景,开始只使用简单的选择器的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)
。