我刚刚遇到@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
?
提前感谢任何见解。
答案 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)
,具体取决于您的方案。