我的问题可以通过类似于ngrx文档的选择器示例来最好地描述,以使事情变得简单(https://github.com/ngrx/platform/blob/master/docs/store/selectors.md#using-selectors-for-multiple-pieces-of-state)。
我使用异步管道来订阅我使用选择器选择的某些状态片,例如
this.visibleBooks$ = this.store$.select(selectVisibleBooks)
问题是,如果allBooks
数组是“ small”(小于100个项目),则我的视图会立即更新。但是,当它大于100时,我的视图仅在下次触发更改检测(例如通过滚动)时才更新。这是非常糟糕的用户体验,仅在滚动列表后才能看到书籍。
我查看了异步管道(https://github.com/angular/angular/blob/master/packages/common/src/pipes/async_pipe.ts)的来源,实际上_updateLatestValue
方法调用了ChangeDetectorRef.markForCheck()
,据我所知,它标记了要检查组件的更改< strong>下次更改检测被触发。
目前,我的解决方法是通过手动订阅顶级组件
this.store$.select(selectVisibleBooks).subscribe(cb)
,然后在回调中手动调用ChangeDetectorRef.detectChanges()
。
我发现这不尽人意,并且无论Book[]
数组有多大,都希望异步管道始终运行。有人对我可以做一些建议或纠正吗?
根据请求进行编辑
如上所述,上述“书店”案例只是我为简化程序而编写的应用程序的类比。实际上,我的应用程序渲染图的节点和边缘,其中节点和边缘还附加有一个称为“ vnode”的版本,该版本与“ vedge”一起跨越了一个版本树。因此,任何图元素都有其自己的版本树。
我当前正在开发的是一个搜索表单,我们在其中向后端发送特定请求,以向其询问与一组特定搜索键/值对匹配的任何节点。
因此,这些节点将随后呈现在组件<nodes-list>
中,我们通过输入绑定传递节点
<nodes-list [nodes]="nodes$ | async"></nodes-list>
nodes-list
具有更改检测功能,而顶级<search>
组件则具有默认策略。
nodes$
在ngOnInit()
中设置为
this.nodes$ = this.store$.select(selectFullNodesList)
selectFullNodesList
看起来像这样:
export const fullNodesSelector = getFullNodesSelector(createSelector(selectSearchState, s => {
if (s.currentId) {
const nodes = s.queries.get(s.currentId).nodes;
if (nodes) {
return [...nodes];
}
}
return null;
}))
export const selectFullNodesList = createSelector(
fullNodesSelector,
(global: GlobalState) => global.data.counts,
createSelector(selectSearchState, s => s.sort),
(nodes, counts, sorting) => {
if (!nodes || !counts || !sorting) return null;
return [...nodes.sort(sorting.sortCbFactory(counts))];
}
)
让我解释一下:
getFullNodesSelector(...)
我将在下面显示,它位于顶层库中,因为我们可以在许多功能中重复使用它。但是它的作用是,它以另一个选择器作为参数,该选择器指向节点和vnode键对{key: number, vKey: number}[]
的数组,并将该数组转换为附加了vnode的节点数组(请参见下面的方法)。 search
功能的状态,如果有currentId
(这是当前请求到后端的ID),那么我们选择我们当前请求的结果节点。s.queries
是一个围绕Javascript对象的包装器,它使我可以轻松获取/设置值,进行克隆或向克隆添加新项目。当在NGRX中使用键/值存储时,这对我很有帮助。因此,s.queries.get(s.currentId).nodes
。 global.data.counts
只是每个节点有多少个邻居的列表。我想知道这一点,因为我想按“计数”对节点列表进行排序。 s.sort
是当前选择列表的排序方式。 sortCbFactory
,该工厂仅返回正确的回调以传递给Array.sort
,但我需要counts
出现在回调的本地范围中,否则我无法按计数排序。 selectSearchState
仅仅是功能选择器
export const selectSearchState = createFeatureSelector<SearchState>('search');
getFullNodesSelector(...)
看起来像这样:
function getFullNodesSelector(keyPairsSelector: MemoizedSelector<object, GraphElementKeyPair[]>): MemoizedSelector<object, INodeJSON<IVNodeJSON>[]> {
return createSelector(
keyPairsSelector,
(s: GlobalState) => s.data.nodes,
(s: GlobalState) => s.data.vnodes,
(pairs, nodes, vnodes) => {
if (!pairs || !nodes || !vnodes) return null;
return pairs.map(pair => ({
...nodes.get(pair.key),
_SUB: {
...vnodes.get(pair.vKey)
}
}));
})
}
再次评论:
GraphElementKeyPair
({key: number, vKey: number}
)数组get
方法。 因此,由于我们已经使用异步管道订阅了this.nodes$
,因此每次在流<nodes-list>
上有新事件时都应进行更新。但是,实际上,这似乎取决于INodeJSON<IVNodeJSON>[]
的大小,并且如果数组的长度>〜80,我们必须通过单击某个地方或滚动来手动触发更改检测。对于较小的数组,应该自动刷新节点列表。
答案 0 :(得分:1)
大型数据集不会有任何问题。您的选择器应该是同步的。这意味着选择器在运行时,在后台没有其他任何事情发生。不管在选择器中计算所有内容需要花费多少时间,都可以。如果花费的时间太长,您的浏览器可能会死机,但仅此而已。
当你说
我使用的是ngrx-store-freeze,应该防止出现这种情况
It is not true.
从商店的角度来看,这是正确的。但是,让我们想象一下:
您的商店有一个ID数组(比方说用户ID)。
您有一个名为getAllUsers
的第一个选择器。
这只是映射用户ID并检索正确的用户,对吗?签名为(usersIds: string[]): User[]
。
当然,在这里,您将创建一个新的数组引用,并且(不应该)使usersIds
数组发生突变。
但是,您还有另一个选择器。 getUsersResolved
基本上可以“解析”异物。假设用户有动物列表。从第一个选择器中,您将获得一个用户列表,每个用户都有一个animalsIds
属性。但是,您想要的是拥有一个animals
数组。如果您从此选择器中更改了原始数组(来自第一个选择器的数组),则ngrx-store-freeze
将不会引发任何错误,这是有道理的:您正在更改一个数组,但不是存储中的一个数组。 / p>
那怎么可能是个问题?
getUsersResolved
,将其分配给一个变量,然后使用async
管道从视图中订阅该变量(假设这是您在整个应用程序中首次订阅做到了!)getAllUsers
被getUsersResolved
(也是第一次调用)(第一次)getAllUsers
按预期创建一个新数组,并将其传递给getUsersResolved
。由于这是第一次,即使您将该数组修改为getUsersResolved
,也不会有任何问题:更改检测将在第一次接收到该数组时完成getUsersResolved
将被触发,但是如果您不尊重不变性并修改来自getAllUsers
的第一个数组,则该数组引用不会更改,并且不会进行更改检测。而且,因为该数组不是商店的一部分,它是从选择器创建的数组,所以完全没问题所以我不确定您的问题是否来自那里,但您可能要仔细检查一下您是否尊重选择器中的不变性。
最终,如果不确定,请共享selectVisibleBooks
,及其使用的每个选择器的代码。