15

onScrollEnd我有一个组件在呈现虚拟列表中的最后一项时触发事件。此事件将执行新的 API 请求以获取下一页并使用scan运算符将​​它们与先前的结果合并。

该组件还有一个触发onSearch事件的搜索字段。

scan触发搜索事件时,如何清除算子之前累积的结果?还是我需要在这里重构我的逻辑?

const loading$ = new BehaviorSubject(false);
const offset$ = new BehaviorSubject(0);
const search$ = new BehaviorSubject(null);

const options$: Observable<any[]> = merge(offset$, search$).pipe(
  // 1. Start the loading indicator.
  tap(() => loading$.next(true)),
  // 2. Fetch new items based on the offset.
  switchMap(([offset, searchterm]) => userService.getUsers(offset, searchterm)),
  // 3. Stop the loading indicator.
  tap(() => loading$.next(false)),
  // 4. Complete the Observable when there is no 'next' link.
  takeWhile((response) => response.links.next),
  // 5. Map the response.
  map(({ data }) =>
    data.map((user) => ({
      label: user.name,
      value: user.id
    }))
  ),
  // 6. Accumulate the new options with the previous options.
  scan((acc, curr) => {
    // TODO: Dont merge on search$.next 
    return [...acc, ...curr]);
  }
);

// Fetch next page
onScrollEnd: (offset: number) => offset$.next(offset);
// Fetch search results
onSearch: (term) => {
  search$.next(term);
};
4

4 回答 4

6

我认为你可以通过重组你的链来实现你想要的(为了简单起见,我省略tap了触发加载的调用):

search$.pipe(
  switchMap(searchterm =>
    concat(
      userService.getUsers(0, searchterm),
      offset$.pipe(concatMap(offset => userService.getUsers(offset, searchterm)))),
    ).pipe(
      map(({ data }) => data.map((user) => ({
        label: user.name,
        value: user.id
      }))),
      scan((acc, curr) => [...acc, ...curr], []),
    ),
  ),
);

每个发射search$都将创建一个新的内部 Observable,它自己scan的将从一个空的累加器开始。

于 2019-05-28T08:19:45.257 回答
4

要操作statea scan,您可以编写获取旧状态和新更新的高阶函数。然后与合并运算符结合。这样,您就可以坚持使用干净的面向流的解决方案,而不会产生任何副作用。

const { Subject, merge } = rxjs;
const { scan, map } = rxjs.operators;

add$ = new Subject();
clear$ = new Subject();

add = (value) => (state) => [...state, value];
clear = () => (state) => [];

const result$ = merge(
  add$.pipe(map(add)),
  clear$.pipe(map(clear))
).pipe(
  scan((state, innerFn) => innerFn(state), [])
)

result$.subscribe(result => console.log(...result))

add$.next(1)
add$.next(2)
clear$.next()
add$.next(3)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>

此方法可以轻松扩展和/或适用于staterxjs 中的其他用例。

示例(删除最后一项)

removeLast$ = new Subject()

removeLast = () => (state) => state.slice(0, -1);

merge(
  ..
  removeLast$.pipe(map(removeLast)),
  ..
)

于 2021-03-13T15:38:30.497 回答
2

withLatestFrom找到了一个可行的解决方案:我在操作员之前使用检查当前偏移量,scan并在需要时根据该值重置累加器。

Stackblitz 演示

于 2019-05-28T17:58:47.237 回答
1

这是一个有趣的流。想一想,offset$ 和 search$ 实际上是 2 个独立的流,但逻辑不同,因此应该在最后而不是开始时合并。

此外,在我看来,搜索应该将偏移量重置为 0,而我在当前逻辑中看不到这一点。

所以这是我的想法:

const offsettedOptions$ = offset$.pipe(
    tap(() => loading$.next(true)),    
    withLatestFrom(search$),
    concatMap(([offset, searchterm]) => userService.getUsers(offset, searchterm)),
    tap(() => loading$.next(false)),
    map(({ data }) =>
    data.map((user) => ({
      label: user.name,
      value: user.id
    })),
    scan((acc, curr) => [...acc, ...curr])
);

const searchedOptions$ = search$.pipe(
    tap(() => loading$.next(true)),
    concatMap(searchTerm => userService.getUsers(0, searchterm)),
    tap(() => loading$.next(false)),
    map(({ data }) =>
    data.map((user) => ({
      label: user.name,
      value: user.id
    })),
);

const options$ = merge(offsettedOptions, searchedOptions);

看看这是否有效或有意义。我可能缺少一些上下文。

于 2019-05-27T21:22:40.460 回答