@ignorance/vuex-observable

vuex plugin connected rxjs

Usage no npm install needed!

<script type="module">
  import ignoranceVuexObservable from 'https://cdn.skypack.dev/@ignorance/vuex-observable';
</script>

README

@ignorance/vuex-observable

Consume Vuex actions as Observables using RxJS 6, inspired by redux-observable.

数字跳动在线 demo

一览

以下示例使用 vue3 的函数式 API。vue2 的用法与之类似,这里不再赘述。

import { createStore } from "vuex";
import { 
  combineEpics, 
  createEpicPlugin,
  Epic
} from '@ignorance/vuex-observable'
import {
  filter,
  map,
} from "rxjs/operators";

const pingEpic: Epic= (action$) => action$.pipe(
  filter(action => action.type === "test"),
  delay(1000), // Asynchronously wait 1000ms then continue
  mapTo({ type: "PONG", payload: 10, isAction: false }) // mapTo('PONG')
);
 
const vuexObservable = createEpicPlugin();

export default createStore({
  state: {
    count: 0,
  },
  mutations: {
    PONG(state, num) {
      state.count = state.count + num;
    },
  },
  plugins: [vuexObservable],
});

vuexObservable.run(combineEpics(pingEpic));

安装

npm i @ignorance/vuex-observable

使用

// store.js
import { createEpicPlugin } from '@ignorance/vuex-observable'

const vuexObservable = createEpicPlugin();

export default createStore({
  // ...
  plugins: [vuexObservable],
});

vuexObservable.run();

装配 epic

import { combineEpics, Epic } from '@ignorance/vuex-observable'
// 定义 epic
// 拦截 type 为 “test” 的 action,延时1秒后,转化为一个新的 mutation 发射出去
const pingEpic: Epic= action$ => action$.pipe(
  filter(action => action.type === "test"),
  delay(1000), // Asynchronously wait 1000ms then continue
  mapTo({ type: "PONG", payload: 10, isAction: false }) // mapTo('PONG')
);

// 装配
vuexObservable.run(combineEpics(pingEpic));

食谱

数字跳动

const takeUntilFunc = (endRange: number, currentNumber: number) => {
  return endRange > currentNumber
    ? (val: number) => val <= endRange
    : (val: number) => val >= endRange;
};

const positiveOrNegative = (endRange: number, currentNumber: number) => {
  return endRange > currentNumber ? 1 : -1;
};

const otometerEpic: Epic = (action$, state$, _, variable = 0) => action$.pipe(
  filter(action => action.type === "otometer"),
  withLatestFrom(state$),
  switchMap(([{ payload: endRange }, state]) => {
    return timer(0, 20).pipe(
      tap(() => variable = state.currentNumber),
      mapTo(positiveOrNegative(endRange, variable)),
      startWith(variable),
      scan((acc, curr) => acc + curr),
      takeWhile(takeUntilFunc(endRange, state.currentNumber)),
    )
  }),
  map(val => ({ type: 'BEAT', payload: val, isAction: false })),
  startWith(variable),
)

export default createStore({
  state: {
    currentNumber: 0,
  },
  mutations: {
    BEAT(state, num) {
      state.currentNumber = num
    },
  },
  plugins: [vuexObservable],
});

vuexObservable.run(combineEpics(otometerEpic));

鼠标跟随动画

<template>
  <div @mousemove="e => $store.dispatch('follow', e)">
    <div 
      v-for="(pos, index) in $store.state.pos"
      :key="index"
      :style="{ left: pos.x + 'px', top: pos.y + 'px' }"
    ></div>
  </div>
</template>
const followEpic: Epic= (action$, state$, store) => action$.pipe(
  filter(action => action.type === "follow"),
  withLatestFrom(state$),
  tap(([action, state]) => store.commit("FOLLOW", { index: 0, x: action.payload.clientX, y: action.payload.clientY })),
  delay(200),
  tap(([action, state]) => store.commit("FOLLOW", { index: 1, x: action.payload.clientX, y: action.payload.clientY })),
  delay(200),
  tap(([action, state]) => store.commit("FOLLOW", { index: 2, x: action.payload.clientX, y: action.payload.clientY })),
  delay(200),
  tap(([action, state]) => store.commit("FOLLOW", { index: 3, x: action.payload.clientX, y: action.payload.clientY })),
);
 
const vuexObservable = createEpicPlugin();

export default createStore({
  state: {
    pos: [
      { x: 0, y: 0 },
      { x: 0, y: 0 },
      { x: 0, y: 0 },
      { x: 0, y: 0 },
    ]
  },
  mutations: {
    FOLLOW(state, {index, ...pos}) {
      state.pos[index] = pos
    }
  },
  plugins: [vuexObservable],
});

vuexObservable.run(combineEpics(followEpic));