UNPKG

redux-dux

Version:

``` npm i --save redux-dux ```

159 lines (141 loc) 4.26 kB
import * as redux from 'redux'; import snakeCase from 'lodash.snakecase'; import set from 'lodash.set'; import reduce from 'lodash.reduce'; import map from 'lodash.map'; import get from 'lodash.get'; import assign from 'lodash.assign'; import upd from 'update-js'; /** * @module dux */ let store; let reducers = []; let combinedReducer = s => s; function duxReducer(...args) { return combinedReducer(...args); } /** * Combines an array of reducers into one. * If reducer doesn't modify the state it should return its first * argument. In the case nothing is passed, the resulting * reducer will be an identity function. * @param {Function[]} [reducers] - Reducers to combine into one. */ export function combine(...reducers) { return (state, action) => { for (let i = 0; i < reducers.length; i += 1) { const newState = reducers[i](state, action); if (newState && newState !== state) { return newState; } } return state; }; } function sdbm(str) { let hash = 0; for (let i = 0; i < str.length; i++) { hash = str.charCodeAt(i) + (hash << 6) + (hash << 16) - hash; } hash = hash >>> 0; return hash.toString(16); } function hashKeys(o) { return sdbm(reduce(o, (acc, __, key) => acc + key, '')); } const createReducerCreator = (nameToActionType, sliceKey) => ( reduceFn, key ) => { const actionType = nameToActionType(key); return (state, { type, args }) => { if (type !== actionType) return state; let slice = reduceFn(get(state, sliceKey), ...args); if (slice instanceof Function) { slice = slice(state); } return update(state, sliceKey, slice); }; }; // TODO(ET): deep-freeze slice in dev mode /** * Creates a Redux module. * @param {Object} options - set of key-value pairs, where key is an action * creator name and value is a reducer function. * The reducer function receives a slice of the state, corresponding * to the module, and arguments passed to the action creator: (slice, ...args). * The reducer function can return either a new slice or a function which * receives the whole state and returns a new slice. * */ export function dux(options, selectors) { const hash = hashKeys(options); const nameToActionType = key => hash + '/' + snakeCase(key).toUpperCase(); const createReducer = createReducerCreator(nameToActionType, hash); const createAction = key => (...args) => store.dispatch({ type: nameToActionType(key), args }); const createSelector = fn => state => fn(get(state, hash)); let self = reduce( options, (acc, val, key) => set(acc, key, createAction(key)), {} ); self.selectors = reduce( selectors, (acc, val, key) => { const selector = createSelector(val); return set(acc, key, selector); }, {} ); self.extend = function(duck, selectors) { const { selectors: __, ...actions } = duck; assign(this, actions); assign(this.selectors, selectors); return this; }; reducers.push(combine(...map(options, createReducer))); combinedReducer = combine(...reducers); return self; } export function bindSelectors(...args) { const selectorsArr = args.slice(0, args.length - 1); const cb = args[args.length - 1]; return state => { const sliceGetters = selectorsArr.map(selectors => { return Object.defineProperties( {}, reduce( selectors, (acc, selector, name) => set(acc, name, { get: () => selector(state) }), {} ) ); }); return cb(...sliceGetters); }; } /** * Creates Redux store. * See [createStore.md](https://github.com/reduxjs/redux/blob/master/docs/api/createStore.md). */ export function createStore(reducer, preloadedState, enhancers) { if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancers = preloadedState; preloadedState = undefined; } // TODO: what if createStore is called multiple times? reducers.push(reducer); combinedReducer = combine(...reducers); return (store = redux.createStore(duxReducer, preloadedState, enhancers)); } /** * Object immutability helper. * See [update-js](https://github.com/akuzko/update-js). */ export const update = upd;