同时映射和扫描的模式

时间:2018-01-19 17:28:52

标签: rxjs

让我说我有一个可观察到的低级动作(例如,鼠标移动事件),并且我想同时扫描它们以构建一个状态,并发出一个更高的Observable - 级别动作(例如,鼠标手势)。如果我只使用scan(),则reducer只能返回状态,因此没有好处。如果我只在低级动作上使用map(),则投影函数无法访问状态,因此不会起作用。

我可以通过首先扫描()然后用原始动作流压缩它,或者让状态包含最后一个高级动作,然后使用map()来隔离高级别来解决它来自国家的行动,但那些都感觉有点粗暴。这似乎是一个相对简单的想法,所以人们会遵循一般模式吗?

(很明显,我实际上并没有使用鼠标手势,因此我不能像过去那样只看小窗口使用捷径:我真的想要完整状态。)

1 个答案:

答案 0 :(得分:0)

我刚刚做的是scan创建当前输入和状态的元组。然后,您就可以map不再使用该状态。

例如它将所有内容包装在一个运算符中(TypeScript;如果坚持使用JavaScript,则删除类型):

export type FnWithState<F, S, T> = (from: F, state: S) => [T, S];

export function mapWithState<F, S, T>(fn: FnWithState<F, S, T>, initialState: S)
: OperatorFunction<F, T> {
  return (in$: Rx.Observable<F>) =>
    in$.pipe(scan(([_out, state]: [T, S], from: F) => fn(from, state),
                  [{} as T, initialState]),
             map(([out, _state]) => out));
}

像这样使用它:

type WordHist = immutable.Map<string, number>;

function emitEveryThird(word: string, hist: WordHist): [string | null, WordHist] {
  const newHist = hist.update(word, (n = 0) => n + 1);
  return [newHist.get(word)! % 3 === 0 ? word : null, newHist];
}

const in$ = Rx.Observable.from(['a', 'b', 'a', 'a', 'a', 'b', 'a', 'c', 'a', 'b']);

const actual: string[] = [];
await in$.pipe(mapWithState(emitEveryThird, immutable.Map()))
  .subscribe((w) => w && actual.push(w));

t.deepEqual(['a', 'a', 'b'], actual);