Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Most.js based middleware for Redux. Handle async actions with monadic streams & reactive programming.

License

NotificationsYou must be signed in to change notification settings

joshburgess/redux-most

Repository files navigation

Most.js based middleware forRedux.

Handle async actions with monadic streams & reactive programming.

Install

With yarn (recommended):

yarn add redux-most

or with npm:

npm install --save redux-most

Additionally, make sure the peer dependencies,redux andmost, are also installed.

Background

redux-most is based onredux-observable.It uses the same pattern/concept of"epics"without requiringRxJS 5 as a peer dependency.Althoughredux-observable does provide capability for using other stream libraries via adapters,redux-most allows you to bypass needing to install bothRxJS 5 andMost. I preferMost forworking with observables and would rather have minimal dependencies. So, I wrotethis middleware primarily for my own use.

Please, seeredux-observable'sdocumentationfor details on usage.

Why Most over RxJS?

RxJS 5 is great. It's quite a bit faster thanRxJS 4, andRx, in general, is avery useful tool which happens to exist across many different languages.Learning it is definitely a good idea. However,Most is significantly smaller,less complicated, and faster thanRxJS 5. I prefer its more minimal set ofoperators and its focus on performance. Also, likeRamdaorlodash/fp,Mostsupports a functional API in which the data collection (a stream, rather thanan array, in this case) gets passed in last. This is important, because itallows you to use functional programming techniques like currying & partialapplication, which you can't do withRxJS without writing your own wrapperfunctions, because it only offers an OOP/fluent/chaining style API.

Why integrateMost/RxJS withredux instead of recreating it with streams?

It's true that it's quite easy to implement the core ideas ofRedux withobservables using thescan operator. (See myinferno-most-fp-demofor an example.) However, theRedux DevToolsprovide what is arguably the nicest developer tooling experience currently availablein the JavaScript ecosystem. Therefore, it is huge to be able to maintain it as an assetwhile still reaping the benefits of reactive programming with streams. Purists, those whoare very experienced with working with observables, and those working on smaller appsmay not care as much about taking advantage of that tooling as using an elegantstreams-only based solution, and that's fine. The important thing is having a choice.

Whyredux-most orredux-observable overredux-saga?

redux-saga is nice. It's a sophisticated approach to handling asynchronousactions withRedux and can handle very complicated tasks with ease. However,due to generators being pull-based, it is much more imperative in nature. Isimply prefer the more declarative style of push-based streams & reactiveprogramming.

Differences betweenredux-most &redux-observable

Summary

  • There are no adapters.redux-most is only intended to be used withMost.
  • redux-most offers 2 separate APIs: aredux-observable-like API, where Epicsget passed an action stream & a store middleware object containingdispatch &getStatemethods, and a stricter, more declarative API, where Epics get passed an action stream & a state stream.
  • combineEpics takes in an array of epics instead of multiple arguments.
  • StandardMost streams are used instead of a custom Observable extension.
  • select andselectArray are available instead of the variadicofType.

Further Elaboration:

As the name implies,redux-most does not offer adapters for use with other reactiveprogramming libraries that implement the Observable type. It's merely an implementation ofredux-observable's "Epic" pattern exclusively intended for use withMost.Most is arguablythe fastest, simplest, most functional, & most elegant reactive programming library in theJavaScript ecosystem right now, andMost 2.0 will be even better, as it will feature anauto-curried API likelodash/fp andramda, but for working with streams instead of arrays.For a preview of what's to come, check out what's going onhere.

Initially,redux-most offered the same API asredux-observable, where Epics received an actionstream & a store middleware object containingdispatch &getState methods. However, it now offersboth that API and another stricter, more declarative API which eliminates the use ofdispatch &getState. The reason for this is that I rarely found myself using the imperativedispatchmethod. It's not really needed, because you can useswitch,merge,mergeArray, etc. to sendmultiple actions through your outgoing stream. This is nice, because it allows you to stay locked intothe declarative programming style the entire time.

However, usinggetState was still required in epics that needed access to the current state. Iwanted a nice, convenient way to access the current state, just like I had for dispatching actions.So, I created an alternate API where Epics receive a stream of state changes rather than the{ dispatch, getState } object. This state stream, combined with the newwithState utility function,let's you use streams for both dispatching actions & accessing the current state, allowing you to stayfocused & in the zone (the reactive programming mindset).

Moving on, whereascomebineEpics is variadic inredux-observable, it's unary inredux-most. Ittakes in only one argument, an array of epics, instead of individual epics getting passed in as separatearguments.

As for streams, I chose not to extend theObservable type with a customActionsObservabletype. So, when working withredux-most, you will be working with normalmoststreams without any special extension methods. However, I have offered somethingsimilar toredux-observable'sofType operator inredux-most with theselect andselectArray helper functions.

LikeofType,select andselectArray are convenience utilities for filteringactions by a specific type or types. Inredux-observable,ofType can optionally take multipleaction types to filter on. Inredux-most, we want to be more explicit, as it is generally a goodpractice in functional programming to prefer a known number of arguments over a variable amountof arguments. Therefore,select is used when we want to filter by a single action type, andselectArray is used when we want to filter by multiple action types (via an array) simultaneously.

Additionally, to better align with theMost API, and because these functions take a known numberof arguments,select &selectArray are curried, which allows them to be used in either afluent style or a more functional style which enables the use of further currying, partialapplication, & functional composition.

To use the fluent style, just useMost'sthru operator to pass the streamthrough toselect/selectArray as the 2nd argument.

// Fluent styleconstfilteredAction$=action$.thru(select(SOME_ACTION_TYPE))constfilteredActions$=action$.thru(selectArray([SOME_ACTION_TYPE,SOME_OTHER_ACTION_TYPE]))

Otherwise, simply directly pass the stream as the 2nd argument.

// Functional styleconstfilteredAction$=select(SOME_ACTION_TYPE,action$)constfilteredActions$=selectArray([SOME_ACTION_TYPE,SOME_OTHER_ACTION_TYPE],action$)

Alternatively, you can delay passing the 2nd argument while defining functional pipelinesvia functional composition by using thecompose orpipe functions from your favorite FP library,likeramda orlodash/fp. Again, this is becauseselect &selectArray are auto-curried. Beingable to program in this very functional & Pointfree style is one of the main reasons why someonemight prefer using redux-most over redux-observable.

// Functional & Pointfree style using currying & functional compositionimport{compose,curry,pipe}from'ramda'import{debounce,filter,map}from'most'// NOTE: Most 2.0 will feature auto-curried functions, but right now we must curry them manually.constcurriedDebounce=curry(debounce)constcurriedFilter=curry(filter)constcurriedMap=curry(map)// someEpic is a new function which is still awaiting one argument, the action$constsomeEpic=compose(curriedMap(someFunction),curriedDebounce(800),select(SOME_ACTION_TYPE))// someOtherEpic is a new function which is still awaiting one argument, the action$// pipe is the same as compose, but read from left-to-right rather than right-to-left.constsomeOtherEpic=pipe(selectArray([SOME_ACTION_TYPE,SOME_OTHER_ACTION_TYPE]),curriedFilter(somePredicate),curriedMap(someFunction))

API Reference


createEpicMiddleware (rootEpic)

createEpicMiddleware is used to create an instance of the actualredux-most middleware.You provide a single rootEpic.

Arguments

  1. rootEpic(Epic): The root Epic.

Returns

(MiddlewareAPI): An instance of theredux-most middleware.

Example

// redux/configureStore.jsimport{createStore,applyMiddleware,compose}from'redux'import{createEpicMiddleware}from'redux-most'import{rootEpic,rootReducer}from'./modules/root'constepicMiddleware=createEpicMiddleware(rootEpic)exportdefaultfunctionconfigureStore(){conststore=createStore(rootReducer,applyMiddleware(epicMiddleware))returnstore}

createStateStreamEnhancer (epicMiddleware)

createStateStreamEnhancer is used to accessredux-most's alternate API, which passesEpics a state stream (Ex:state$) instead of the{ dispatch, getState } storeMiddlewareAPI object. You must provide an instance of theEpicMiddleware, and theresulting function must be applied AFTER usingredux'sapplyMiddleware if also usingother middleware.

Arguments

  1. rootEpic(Epic): The root Epic.

Returns

(MiddlewareAPI): An enhanced instance of theredux-most middleware, exposing a streamof state change values.

Example

import{createStore,applyMiddleware}from'redux'import{createEpicMiddleware,createStateStreamEnhancer,}from'redux-most'importrootEpicfrom'../epics'constepicMiddleware=createEpicMiddleware(rootEpic)constmiddleware=[...]// other middleware hereconststoreEnhancers=compose(createStateStreamEnhancer(epicMiddleware),applyMiddleware(...middleware))conststore=createStore(rootReducer,storeEnhancers)

combineEpics (epicsArray)

combineEpics, as the name suggests, allows you to pass in an array of epics and combine them into a single one.

Arguments

  1. epicsArray(Epic[]): The array ofepics to combine into one root epic.

Returns

(Epic): An Epic that merges the output of every Epic provided and passes along the redux store as arguments.

Example

// epics/index.jsimport{combineEpics}from'redux-most'importsearchUsersDebouncedfrom'./searchUsersDebounced'importsearchUsersfrom'./searchUsers'importclearSearchResultsfrom'./clearSearchResults'importfetchReposByUserfrom'./fetchReposByUser'importadminAccessfrom'./adminAccess'constrootEpic=combineEpics([searchUsersDebounced,searchUsers,clearSearchResults,fetchReposByUser,adminAccess,])exportdefaultrootEpic

EpicMiddleware

An instance of theredux-most middleware.

To create it, pass your root Epic tocreateEpicMiddleware.

Methods

replaceEpic (nextEpic)

Replaces the epic currently used by the middleware.

It is an advanced API. You might need this if your app implements code splitting and youwant to load some of the epics dynamically or you're using hot reloading.

Example

import{createEpicMiddleware}from'redux-most'importrootEpicfrom'../epics'...constepicMiddleware=createEpicMiddleware(rootEpic)...// hot reload epicsconstreplaceRootEpic=()=>{import('../epics').then(({default:nextRootEpic})=>{epicMiddleware.replaceEpic(nextRootEpic)})}if(module.hot){module.hot.accept('../epics',replaceRootEpic)}

Arguments

  1. nextEpic(Epic): The next epic for the middleware to use.

select (actionType, stream)

A helper function for filtering the stream of actions by a single action type.

Arguments

  1. actionType(string): The type of action to filter by.
  2. stream(Stream): The stream of actions you are filtering. Ex:actions$.

Returns

(Stream): A new, filtered stream holding only the actions corresponding to the actiontype passed toselect.

Theselect operator is curried, allowing you to use a fluent or functional style.

Examples

// Fluent styleimport{SEARCHED_USERS_DEBOUNCED}from'../constants/ActionTypes'import{clearSearchResults}from'../actions'import{select}from'redux-most'constwhereEmpty=({payload:{ query}})=>!queryconstclear=action$=>action$.thru(select(SEARCHED_USERS_DEBOUNCED)).filter(whereEmpty).map(clearSearchResults)exportdefaultclear
// Functional styleimport{SEARCHED_USERS_DEBOUNCED}from'../constants/ActionTypes'import{clearSearchResults}from'../actions'import{select}from'redux-most'constwhereEmpty=({payload:{ query}})=>!queryconstclear=action$=>{constsearch$=select(SEARCHED_USERS_DEBOUNCED,action$)constemptySearch$=filter(whereEmpty,search$)returnmap(clearSearchResults,emptySearch$)}exportdefaultclear
// Functional & Pointfree style using functional compositionimport{SEARCHED_USERS_DEBOUNCED}from'../constants/ActionTypes'import{clearSearchResults}from'../actions'import{select}from'redux-most'import{curriedFilterasfilter,curriedMapasmap,}from'../utils'import{compose}from'ramda'constwhereEmpty=({payload:{ query}})=>!queryconstclear=compose(map(clearSearchResults),filter(whereEmpty),select(SEARCHED_USERS_DEBOUNCED))exportdefaultclear

selectArray (actionTypes, stream)

A helper function for filtering the stream of actions by an array of action types.

Arguments

  1. actionTypes(string[]): An array of action types to filter by.
  2. stream(Stream): The stream of actions you are filtering. Ex:actions$.

Returns

(Stream): A new, filtered stream holding only the actions corresponding to the actiontypes passed toselectArray.

TheselectArray operator is curried, allowing you to use a fluent or functional style.

Examples

// Fluent styleimport{SEARCHED_USERS,SEARCHED_USERS_DEBOUNCED,}from'../constants/ActionTypes'import{clearSearchResults}from'../actions'import{selectArray}from'redux-most'constwhereEmpty=({payload:{ query}})=>!queryconstclear=action$=>action$.thru(selectArray([SEARCHED_USERS,SEARCHED_USERS_DEBOUNCED,])).filter(whereEmpty).map(clearSearchResults)exportdefaultclear
// Functional styleimport{SEARCHED_USERS,SEARCHED_USERS_DEBOUNCED,}from'../constants/ActionTypes'import{clearSearchResults}from'../actions'import{selectArray}from'redux-most'constwhereEmpty=({payload:{ query}})=>!queryconstclear=action$=>{constsearch$=selectArray([SEARCHED_USERS,SEARCHED_USERS_DEBOUNCED,],action$)constemptySearch$=filter(whereEmpty,search$)returnmap(clearSearchResults,emptySearch$)}exportdefaultclear
// Functional & Pointfree style using functional compositionimport{SEARCHED_USERS,SEARCHED_USERS_DEBOUNCED,}from'../constants/ActionTypes'import{clearSearchResults}from'../actions'import{selectArray}from'redux-most'import{curriedFilterasfilter,curriedMapasmap,}from'../utils'import{compose}from'ramda'constwhereEmpty=({payload:{ query}})=>!queryconstclear=compose(map(clearSearchResults),filter(whereEmpty),selectArray([SEARCHED_USERS,SEARCHED_USERS_DEBOUNCED,]))exportdefaultclear

withState (stateStream, actionStream)

A utility function for use withredux-most's optional state stream API. Thisprovides a convenient way tosample the latest state change value. Note:accessing the alternate API requires usingcreateStateStreamEnhancer.

Arguments

  1. stateStream(Stream): The state stream provided byredux-most's alternate API.
  2. actionStream(Stream): The filtered stream of action events used to triggersampling of the latest state. (Ex:actions$).

Returns

([state, action]): An Array of length 2 (or Tuple) containing the lateststate value at index 0 and the latest action of the filtered action stream at index 1.

withState is curried, allowing you to pass in the state stream & action streamtogether, at the same time, or separately, delaying passing in the action stream.This provides the user extra flexibility, allowing it to easily be used withinfunctional composition pipelines.

Examples

import{select,withState}from'redux-most'import{curriedMapasmap}from'../utils'importcomposefrom'ramda/src/compose'constaccessStateFromArray=([state,action])=>({type:'ACCESS_STATE',payload:{latestState:state,accessedByAction:action,},})// dispatch { type: 'STATE_STREAM_TEST' } in Redux DevTools to testconststateStreamTest=(action$,state$)=>compose(map(accessStateFromArray),withState(state$),select('STATE_STREAM_TEST'))(action$)exportdefaultstateStreamTest

Releases

No releases published

Packages

No packages published

Contributors3

  •  
  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp