diff --git a/src/Redux.js b/src/Redux.js index 860da8746b..07a8014122 100644 --- a/src/Redux.js +++ b/src/Redux.js @@ -1,46 +1,29 @@ -import createDispatcher from './createDispatcher'; -import composeStores from './utils/composeStores'; -import thunkMiddleware from './middleware/thunk'; - export default class Redux { - constructor(dispatcherOrStores, initialState) { - let finalDispatcher = dispatcherOrStores; - if (typeof dispatcherOrStores === 'object') { - // A shortcut notation to use the default dispatcher - finalDispatcher = createDispatcher( - composeStores(dispatcherOrStores), - (getState) => [thunkMiddleware(getState)] - ); - } - + constructor(reducer, initialState) { this.state = initialState; this.listeners = []; - this.replaceDispatcher(finalDispatcher); + this.replaceReducer(reducer); } - getDispatcher() { - return this.dispatcher; + getReducer() { + return this.reducer; } - replaceDispatcher(nextDispatcher) { - this.dispatcher = nextDispatcher; - this.dispatchFn = nextDispatcher(this.state, ::this.setState); + replaceReducer(nextReducer) { + this.reducer = nextReducer; + this.dispatch({ type: '@@INIT' }); } dispatch(action) { - return this.dispatchFn(action); + const { reducer } = this; + this.state = reducer(this.state, action); + this.listeners.forEach(listener => listener()); } getState() { return this.state; } - setState(nextState) { - this.state = nextState; - this.listeners.forEach(listener => listener()); - return nextState; - } - subscribe(listener) { const { listeners } = this; listeners.push(listener); diff --git a/src/components/createProvider.js b/src/components/createProvider.js index fa3e02221e..6f558154cc 100644 --- a/src/components/createProvider.js +++ b/src/components/createProvider.js @@ -31,8 +31,8 @@ export default function createProvider(React) { const { redux: nextRedux } = nextProps; if (redux !== nextRedux) { - const nextDispatcher = nextRedux.getDispatcher(); - redux.replaceDispatcher(nextDispatcher); + const nextReducer = nextRedux.getReducer(); + redux.replaceReducer(nextReducer); } } diff --git a/src/createDispatcher.js b/src/createDispatcher.js deleted file mode 100644 index 029b698697..0000000000 --- a/src/createDispatcher.js +++ /dev/null @@ -1,26 +0,0 @@ -import composeMiddleware from './utils/composeMiddleware'; - -const INIT_ACTION = { - type: '@@INIT' -}; - -export default function createDispatcher(store, middlewares = []) { - return function dispatcher(initialState, setState) { - let state = setState(store(initialState, INIT_ACTION)); - - function dispatch(action) { - state = setState(store(state, action)); - return action; - } - - function getState() { - return state; - } - - const finalMiddlewares = typeof middlewares === 'function' ? - middlewares(getState) : - middlewares; - - return composeMiddleware(...finalMiddlewares, dispatch); - }; -} diff --git a/src/createRedux.js b/src/createRedux.js index f3ee7c1347..8f101c735e 100644 --- a/src/createRedux.js +++ b/src/createRedux.js @@ -1,13 +1,18 @@ import Redux from './Redux'; +import composeReducers from './utils/composeReducers'; -export default function createRedux(...args) { - const redux = new Redux(...args); +export default function createRedux(reducer, initialState) { + const finalReducer = typeof reducer === 'function' ? + reducer : + composeReducers(reducer); + + const redux = new Redux(finalReducer, initialState); return { subscribe: ::redux.subscribe, dispatch: ::redux.dispatch, getState: ::redux.getState, - getDispatcher: ::redux.getDispatcher, - replaceDispatcher: ::redux.replaceDispatcher + getReducer: ::redux.getReducer, + replaceReducer: ::redux.replaceReducer }; } diff --git a/src/index.js b/src/index.js index 607b83f317..1eb0f41570 100644 --- a/src/index.js +++ b/src/index.js @@ -1,16 +1,14 @@ // Core import createRedux from './createRedux'; -import createDispatcher from './createDispatcher'; // Utilities import composeMiddleware from './utils/composeMiddleware'; -import composeStores from './utils/composeStores'; +import composeReducers from './utils/composeReducers'; import bindActionCreators from './utils/bindActionCreators'; export { createRedux, - createDispatcher, composeMiddleware, - composeStores, + composeReducers, bindActionCreators }; diff --git a/src/middleware/thunk.js b/src/middleware/thunk.js index a80a78159c..042bc2ec5a 100644 --- a/src/middleware/thunk.js +++ b/src/middleware/thunk.js @@ -1,10 +1,6 @@ -export default function thunkMiddleware(getState) { - return (next) => { - const recurse = (action) => - typeof action === 'function' ? - action(recurse, getState) : - next(action); - - return recurse; - }; +export default function thunkMiddleware({ getState, dispatch }) { + return (next) => (action) => + typeof action === 'function' ? + action(dispatch, getState) : + next(action); } diff --git a/src/utils/composeReducers.js b/src/utils/composeReducers.js new file mode 100644 index 0000000000..e93887fabf --- /dev/null +++ b/src/utils/composeReducers.js @@ -0,0 +1,12 @@ +import mapValues from '../utils/mapValues'; +import pick from '../utils/pick'; + +export default function composeReducers(reducers) { + const finalReducers = pick(reducers, (val) => typeof val === 'function'); + + return function Composition(atom = {}, action) { + return mapValues(finalReducers, (store, key) => + store(atom[key], action) + ); + }; +} diff --git a/src/utils/composeStores.js b/src/utils/composeStores.js deleted file mode 100644 index d8c4420546..0000000000 --- a/src/utils/composeStores.js +++ /dev/null @@ -1,11 +0,0 @@ -import mapValues from '../utils/mapValues'; -import pick from '../utils/pick'; - -export default function composeStores(stores) { - const finalStores = pick(stores, (val) => typeof val === 'function'); - return function Composition(atom = {}, action) { - return mapValues(finalStores, (store, key) => - store(atom[key], action) - ); - }; -} diff --git a/test/components/connect.spec.js b/test/components/connect.spec.js index 0fce239d36..6e5bd20415 100644 --- a/test/components/connect.spec.js +++ b/test/components/connect.spec.js @@ -1,7 +1,7 @@ import expect from 'expect'; import jsdomReact from './jsdomReact'; import React, { PropTypes, Component } from 'react/addons'; -import { createRedux } from '../../src'; +import { createRedux, composeReducers } from '../../src'; import { connect, Connector } from '../../src/react'; const { TestUtils } = React.addons; diff --git a/test/composeStores.spec.js b/test/composeReducers.spec.js similarity index 58% rename from test/composeStores.spec.js rename to test/composeReducers.spec.js index 551b275e59..2db4022fe2 100644 --- a/test/composeStores.spec.js +++ b/test/composeReducers.spec.js @@ -1,31 +1,31 @@ import expect from 'expect'; -import { composeStores } from '../src'; +import { composeReducers } from '../src'; describe('Utils', () => { - describe('composeStores', () => { - it('should return a store that maps state keys to reducer functions', () => { - const store = composeStores({ + describe('composeReducers', () => { + it('should return a reducer that maps state keys to reducer functions', () => { + const reducer = composeReducers({ counter: (state = 0, action) => action.type === 'increment' ? state + 1 : state, stack: (state = [], action) => action.type === 'push' ? [...state, action.value] : state }); - const s1 = store({}, { type: 'increment' }); + const s1 = reducer({}, { type: 'increment' }); expect(s1).toEqual({ counter: 1, stack: [] }); - const s2 = store(s1, { type: 'push', value: 'a' }); + const s2 = reducer(s1, { type: 'push', value: 'a' }); expect(s2).toEqual({ counter: 1, stack: ['a'] }); }); it('should ignore all props which are not a function', () => { - const store = composeStores({ + const reducer = composeReducers({ fake: true, broken: 'string', another: {nested: 'object'}, stack: (state = []) => state }); - expect(Object.keys(store({}, {type: 'push'}))).toEqual(['stack']); + expect(Object.keys(reducer({}, {type: 'push'}))).toEqual(['stack']); }); }); }); diff --git a/test/createDispatcher.spec.js b/test/createDispatcher.spec.js index 40991bfcec..b31a9052ef 100644 --- a/test/createDispatcher.spec.js +++ b/test/createDispatcher.spec.js @@ -7,7 +7,7 @@ const { constants, defaultText, todoActions, todoStore } = helpers; const { addTodo, addTodoAsync } = todoActions; const { ADD_TODO } = constants; -describe('createDispatcher', () => { +describe.skip('createDispatcher', () => { it('should handle sync and async dispatches', done => { const spy = expect.createSpy( diff --git a/test/createRedux.spec.js b/test/createRedux.spec.js index 1900de351c..03337438c8 100644 --- a/test/createRedux.spec.js +++ b/test/createRedux.spec.js @@ -20,8 +20,8 @@ describe('createRedux', () => { expect(methods).toContain('subscribe'); expect(methods).toContain('dispatch'); expect(methods).toContain('getState'); - expect(methods).toContain('getDispatcher'); - expect(methods).toContain('replaceDispatcher'); + expect(methods).toContain('getReducer'); + expect(methods).toContain('replaceReducer'); }); it('should subscribe to changes', done => { @@ -54,11 +54,11 @@ describe('createRedux', () => { expect(changeListenerSpy.calls.length).toBe(1); }); - it('should use existing state when replacing the dispatcher', () => { + it('should use existing state when replacing the reducer', () => { redux.dispatch(addTodo('Hello')); let nextRedux = createRedux({ todoStore }); - redux.replaceDispatcher(nextRedux.getDispatcher()); + redux.replaceReducer(nextRedux.getReducer()); let state; let action = (_, getState) => {