@ngrx/Store如何用于缓存?

9
支持ngrx的人声称(例如这里),您可以并且应该将所有应用程序状态保存在单个Store中。这表明@ngrx/Store可用于缓存,因为缓存的内容是一种应用程序状态。
Web应用程序中的缓存是一种在拥有数据时返回数据的东西,并封装了当它没有数据时从服务器请求数据的过程。维基百科关于缓存的文章称其为缓存命中(cache hit),当数据可用时,称其为缓存未命中(cache miss)。
从函数式编程的角度来看,我们可以立即看到从缓存中读取数据是函数不纯的 - 它具有副作用,即可能会从服务器请求数据并将其保留在缓存中。我不知道如何使用ngrx做到这一点,例如,需要其选择器是函数纯的。
可能有助于考虑这个在Angular中使用RxJs Observables进行缓存教程(据说rxjs非常适合ngrx)。我们不必滚动太远就能找到包含副作用的getFriends()函数。
getFriends() {

  if(!this._friends){

    this._friends = this._http.get('./components/rxjs-caching/friends.json')
                        .map((res:Response) => res.json().friends)
                        .publishReplay(1)
                        .refCount();
  }

  return this._friends;
}

此外,Store 的内容似乎对整个应用程序普遍可用。唯一的控制在于如何更新状态,但是直接操纵缓存的原始数据是愚蠢的,因为无法保证哪些缓存项可用,哪些不可用。
希望这些顾虑可以得到缓解,并且有一种使用 @ngrx/Store 作为缓存的好方法,如果我错过了什么,请您指教。
4个回答

3
你可以这样做。
friends$ = createEffect (() =>{
  this.actions$.pipe(
   .ofType(ListActions.LOAD)
   .withLatestFrom(
     this.store.select(fromSelectors.loadedList),    
     (action: fromList.Load, store: fromList.State) => store.friends
   )
  .switchMap((friends: Friend[]) => {

    if (friends) {
      return [new ListActions.Loaded(friends)];
    }

    return this.http
      .get('/friendApi')
      .map((friends: Friend[]) => new ListActions.Loaded(friends))
      .catch(err => Observable.of(new ListActions.Failed()));
   });
}

使用 'withLatestFrom' 操作符,我们将存储(已加载的列表)状态与分派的操作一起放入我们的 effect 中,并检查列表是否已填充。如果是,则分派现有列表;否则,进行其余调用并在 reducer 中更新我们的 store。 有关详细答案,请参考此 Medium 文章

2
在 ngrx 中,有 reducers(纯函数),它们会更改状态(或缓存)。这些 reducers 会通过在 store 上调度的 actions 触发。从 store 中,您可以通过 selectors 请求数据片段并订阅其更改。为了实现缓存逻辑,您需要检查数据是否可用,如果不可用,则会调度一个名为 "LoadDataSliceAction" 的 action,该 action 将触发一个 side Effect,然后将数据加载到 store 中。这对您有所帮助吗?

谢谢,是的,这确实有帮助,但说实话,我很难让一个可工作的示例运行起来。我还注意到缓存应该隐藏的东西(缓存逻辑分派的操作和缓存内容)似乎被暴露了出来。然而,考虑到使用ngrx的优点(特别是时间旅行),也许我们可以容忍糟糕的缓存实现? - Antony

2
值得注意的是,浏览器默认带有缓存功能,它是真正的缓存,封装请求和数据,因为它没有问题中讨论的 nrgx 的任何限制。
现在我们正在使用浏览器缓存来存储实体。借用我从这里引用的定义:

实体是具有长期数据值的对象,您可以从服务器读取(可能会写回)。

实体数据只是应用程序数据的一种。配置数据、用户角色、用户之前的选择、屏幕布局的当前状态……这些都是重要的数据,可能受益于使用标准 ngrx 技术进行编码,但它们不是实体。

这篇文章违背了 Redux 的一个基本原则(ngrx 是 Redux 的一种实现方式),即 ngrx 存储是整个应用程序的单一数据源。这里我们说了不同的事情。我们说浏览器缓存是实体的唯一数据源,而 ngrx Store 是其他所有内容的唯一数据源(实际上更糟糕的是:如果实体是可编辑的,则在编辑时 Store 必须承担责任)。
基础知识很简单。我们在实体的 HTTP 响应中设置版本号和缓存时间:
X-Version: 23
Cache-Control: max-age=86400

当我们收到HTTP响应时,我们会像以前一样处理其正文,但我们不会将其存储在任何地方。我们让浏览器为我们存储数据,如果我们需要第二次使用数据,我们只需重新请求它,因为浏览器从其缓存中提供它,几乎是瞬间到达。
如果我们看到一个版本过时的实体,那么我们立即重新请求它,并确保通过在请求头中设置Cache-Control: no-cache来覆盖缓存。这确保我们始终使用实体的当前版本。
        this.http.get<T>(
          url,
          {...options, headers: {'Cache-Control': 'no-cache'}}
        )

但是我们如何知道一个版本已经过时了呢?我们很幸运,因为我们的版本控制系统非常细致,所以我们不会经常收到更新。当前版本号的更新通过一个我没有参与编程的网络套接字进行。因此请记住,我们很幸运,这种方法可能对您无效(至少需要认真考虑),在计算机科学中只有两个难点:缓存失效和命名事物 ;-)
此外,还需要一些警惕性,因为我知道有几种意外情况可能会禁用浏览器缓存,这显然会对性能造成可怕的影响:
  • 在调试器内,例如在Chrome DevTools中有一个“禁用缓存”复选框。
  • 错误的SSL证书。如果浏览器不信任服务器,则不应该保留接收到的数据。这只是在测试环境中存在的问题,人们往往会诱惑自己通过单击“高级”和“继续访问”来响应“您的连接不是私密的”。
那么这个解决方案怎么样(它不会让每个人都高兴,对吧)?Ngrx不能被用作真正的缓存,但浏览器可以!

0

我发现有两种主要方法。它们都涉及向需要过期的存储部分添加时间戳。第一种方法是在选择器函数中检查年龄,如果超过限制,就只需调度检索操作。这个方法使用createSelector语法并具有调度存储的访问权限,相当简单明了。另一种方法需要您像往常一样调度检索操作,然后在检索之前检查数据的年龄。这可以通过将store observable与类似于combineLatest(https://medium.com/@viestursv/how-to-get-store-state-in-ngrx-effect-fab9e9c8f087)的内容结合来实现。

第一种选项的示例

createSelector(previousSelector, (item) => {
    const now = new Date();
    const limit = new Date(now.getTime() - (5 * 60000));
    if (item.age < limit) {
        this.store.dispatch(new getFreshDataAction());
    }
});

2
从选择器中调度操作并不是一个好的方式,因为它们应该只提供一些状态数据。 - Akash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接