Comparison to Redux Observable - deanrad/rx-helper GitHub Wiki
RxHelper was born after many great conversations with folks in the Redux-Observable chat room, including @jayphelps, and @berkeleytrue. I was convinced that Redux-Observable made many cool things possible, but my very first question was "Why must this coolness live inside a Redux middleware?" Some time later, the essence of Redux-Observable was distilled into a Redux-less library called the Antares Protocol, which became this RxHelper library.
The Similarities:
- Specify consequential, possibly async Flux Standard Actions in response to other actions.
- Handle Async side-effects
- Allow front-end apps like SPAs to focus on dispatching a single Action
The Differences:
- You need Redux for Redux-Observable (Redux-Observable), you don't with RxHelper (a store can be added via a filter)
- The async takes place inside the store, with Redux-Observable, outside with RxHelper.
- The one calling
.dispatch
has ways of directly accessing the results of those async operations with RxHelper, not so with Redux-Observable - RxJS Concurrency is utilized in chained
pipe
s with Redux-Observable, it's explicitly a configured parameter with RxHelper, thus read and understood and changed more readily
Vocabulary Differences
Naming is hard. But RxHelper has intentional naming that deviates from vanilla RxJS. This is either a big help to you, or this library's biggest shortcoming! Open an Issue or PR if you think a different set of vocabulary might work. I think something like this may work very well by internationalizing some of these terms into different languages. Source code legibility/comprehensibility benefits from code being written in a style that makes the most sense for your domain.
store.dispatch
⟺agent.process
- Actions ⟺ Events
- Epics ⟺ Renderers
- store ⟺ agent
- actionsOfType ⟺
agent.on(eventTypePattern)
mergeMap
⟺parallel
switchMap
⟺cutoff
concatMap
⟺serial
exhaustMap
⟺mute
Full Example
The Days Without Accident example, coded as a Redux Observable set of epics:
// rootEpic.js
const accidents = action$ =>
action$
.ofType('start') // Kicked off just once
.mergeMap(() => // unpack the events returned
interval(1000*60*60*24) // Every day
.delay(1000*60*60*12). // at midday
.filter(() => Math.random() < 0.01) // if we're unlucky
.map(() => ({ // create the accident info
date: new Date(),
severity: Math.random().toString(7)[2]
}))
.map(payload => ({ type: 'accident', payload }))
const reports = action$ =>
action$
.filter({ type } => /start|accident/.match(type)) // Kicked off on start, or every accident
.switchMap(() => // Restarting each new accident, and unpacking events
interval(1000*60*60*24) // Every day at the beginning of the day
.scan(({count}) => ({ // Create a {date, count} Object
date: new Date(), // with today's date
count: count + 1 // that increments
}), {count: 0})
export default { accidents, reports }
// kicked off with store.dispatch({ type: 'start' })
And the same, as RxHelper code. Note a higher signal/noise, and the prominence that cutoff
mode is being used, vs the switchMap
operator, embedded in the call chain or pipe:
agent.on('start', () => {
return interval(1000*60*60*24) // Every day
.delay(1000*60*60*12). // at midday
.filter(() => Math.random() < 0.01) // if we're unlucky
.map(() => ({ // create the accident info
date: new Date(),
severity: Math.random().toString(7)[2]
}))
}, {
type: 'accident' // And add it to the Action Stream as type: 'accident'
})
agent.on(/start|accident/, () => { // Upon an accident *or* the start of the app
return interval(1000*60*60*24)
.scan(({count}) => ({ // Create a {date, count} Object
date: new Date(), // with today's date
count: count + 1 // that increments
}), {count: 0}) // starting at zero
}, {
type: 'dailyReport',
concurrency: 'cutoff' // Replace the last counter, starting a new one (switchMap)
}
// kicked off via agent.process({ type: 'start' })
// If you want all events to go through a store as well:
agent.filter(/.*/, ({ event }) => store.dispatch(event))