├── .babelrc ├── .flowconfig ├── .gitignore ├── README.md ├── index.html ├── interfaces ├── redux.js └── rx.js ├── jest └── preprocessor.js ├── package.json ├── src ├── __tests__ │ ├── redux-app.js │ └── rx-app.js ├── common.js ├── index.js ├── redux-app.js └── rx-app.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"], 3 | "plugins": ["transform-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | build 3 | .*/node_modules/redux/.* 4 | .*/node_modules/rx/.* 5 | .*/node_modules/react/.* 6 | .*/node_modules/fbjs.* 7 | .*/node_modules/babel.* 8 | .*/node_modules/babylon.* 9 | 10 | [include] 11 | 12 | [libs] 13 | interfaces/ 14 | 15 | [options] 16 | esproposal.class_static_fields=enable 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-rx-fun 2 | redux & rx fun 3 | 4 | There's a blog post that goes with this: http://qiita.com/kimagure/items/a534d149cd9176d34cc1 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redux vs Rx fun 6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /interfaces/redux.js: -------------------------------------------------------------------------------- 1 | type redux$Reducer = (state: T, action: any) => T; 2 | 3 | interface redux$Store { 4 | subscribe(observer: () => any): void; 5 | dispatch(action: any): void; 6 | getState(): T; 7 | }; 8 | 9 | declare module 'redux' { 10 | declare function createStore(reducer: redux$Reducer): redux$Store; 11 | } 12 | -------------------------------------------------------------------------------- /interfaces/rx.js: -------------------------------------------------------------------------------- 1 | declare module 'rx' { 2 | declare class Observable { 3 | static merge(...sources: Observable[]): Observable; 4 | 5 | map(f: (item: T) => R): Observable; 6 | 7 | scan(f: (prev: R, next: T) => R): Observable; 8 | startWith(init: R): Observable; 9 | 10 | subscribe( 11 | next: (item: T) => any, 12 | error?: (error: any) => any, 13 | complete?: (item: T) => any 14 | ): { 15 | unsubscribe: () => void; 16 | }; 17 | } 18 | 19 | declare class Subject extends Observable { 20 | onNext(item: T): void; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jest/preprocessor.js: -------------------------------------------------------------------------------- 1 | // jest/preprocessor.js 2 | var babelJest = require('babel-jest'); 3 | 4 | module.exports = { 5 | process: function(src, filename) { 6 | if (filename.indexOf('node_modules') === -1) { 7 | src = babelJest.process(src, filename); 8 | } 9 | return src; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-rx-fun", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack -wdc --progress", 8 | "watch-flow": "watch 'flow check' src -d -u", 9 | "test": "jest" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "babel": "^6.3.26", 15 | "babel-core": "^6.3.26", 16 | "babel-jest": "^6.0.1", 17 | "babel-loader": "^6.2.0", 18 | "babel-plugin-transform-class-properties": "^6.4.0", 19 | "babel-preset-es2015": "^6.3.13", 20 | "babel-preset-react": "^6.3.13", 21 | "flow-bin": "^0.20.1", 22 | "jest-cli": "^0.8.2", 23 | "react": "^0.14.5", 24 | "react-dom": "^0.14.5", 25 | "redux": "^3.0.5", 26 | "rx": "^4.0.7", 27 | "watch": "^0.17.1", 28 | "webpack": "^1.12.9" 29 | }, 30 | "jest": { 31 | "scriptPreprocessor": "/jest/preprocessor.js", 32 | "unmockedModulePathPatterns": [ 33 | "/node_modules/react", 34 | "/node_modules/redux", 35 | "/node_modules/rx", 36 | "/node_modules/fbjs", 37 | "/node_modules/react-dom", 38 | "/node_modules/react-addons-test-utils" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/__tests__/redux-app.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | 3 | var {reduxStore, reduxCallbacks} = require('../redux-app'); 4 | 5 | describe('redux app', () => { 6 | it('performs actions and modifies state as expected', () => { 7 | var subTicks = 0; 8 | var counter = reduxStore.getState().counter; 9 | 10 | reduxStore.subscribe(() => subTicks++); 11 | 12 | reduxCallbacks.decrement(); 13 | expect(reduxStore.getState().counter).toBe(--counter); 14 | 15 | reduxCallbacks.increment(); 16 | expect(reduxStore.getState().counter).toBe(++counter); 17 | 18 | expect(subTicks).toBe(2); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/__tests__/rx-app.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | 3 | var {rxState$, rxCallbacks} = require('../rx-app'); 4 | 5 | describe('rx app', () => { 6 | it('performs actions and modifies state as expected', () => { 7 | rxCallbacks.decrement(); 8 | rxCallbacks.increment(); 9 | 10 | var subTicks = 0; 11 | rxState$.subscribe( 12 | state => { 13 | switch(subTicks++) { 14 | case 0: 15 | expect(state.counter).toBe(0); 16 | break; 17 | case 1: 18 | expect(state.counter).toBe(-1); 19 | break; 20 | case 2: 21 | expect(state.counter).toBe(0); 22 | break; 23 | default: 24 | throw 'error'; 25 | } 26 | }, 27 | _ => { 28 | throw 'error should not occur'; 29 | }, 30 | _ => { 31 | expect(subTicks).toBe(2); 32 | } 33 | ); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /* Common */ 4 | 5 | import React from 'react'; 6 | 7 | export type State = { 8 | counter: number 9 | }; 10 | 11 | type Callback = () => void; 12 | 13 | type ViewProps = { 14 | name: string, 15 | callbacks: { 16 | increment: Callback, 17 | decrement: Callback 18 | }, 19 | state: State 20 | }; 21 | 22 | export const initState = { 23 | counter: 0 24 | }; 25 | 26 | export class View extends React.Component { 27 | props: ViewProps; 28 | static defaultProps: ViewProps; 29 | 30 | render(): ReactElement { 31 | return ( 32 |
33 |

Hello, {this.props.name}!

34 |

Count: {this.props.state.counter}

35 | 36 | 37 |
38 | ); 39 | } 40 | } 41 | 42 | // this errors correctly that type number is incompatible with string: 43 | // 44 | 45 | // this has missing properties but doesn't throw any errors: 46 | // 47 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /* Redux app */ 4 | 5 | import {reduxStore, renderReduxApp} from './redux-app'; 6 | 7 | reduxStore.subscribe(function () { 8 | renderReduxApp(); 9 | }); 10 | 11 | renderReduxApp(); 12 | 13 | /* Rx app */ 14 | 15 | import {rxState$, renderRxApp} from './rx-app'; 16 | 17 | rxState$.subscribe(renderRxApp); 18 | -------------------------------------------------------------------------------- /src/redux-app.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import {createStore} from 'redux'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import type {State} from './common'; 7 | import {initState, View} from './common'; 8 | 9 | type Action = { 10 | type: string, 11 | payload?: Object 12 | }; 13 | 14 | const INCREMENT = 'INCREMENT'; 15 | const DECREMENT = 'DECREMENT'; 16 | 17 | export function projectRedux(state: State = initState, action : Action): State { 18 | switch(action.type) { 19 | case INCREMENT: 20 | return Object.assign({}, state, { 21 | counter: state.counter + 1 22 | }); 23 | case DECREMENT: 24 | return Object.assign({}, state, { 25 | counter: state.counter - 1 26 | }); 27 | default: 28 | 29 | return state; 30 | } 31 | } 32 | 33 | export const reduxStore = createStore(projectRedux); 34 | 35 | export const reduxCallbacks = { 36 | increment: function () { 37 | reduxStore.dispatch({ 38 | type: INCREMENT 39 | }); 40 | }, 41 | decrement: function () { 42 | reduxStore.dispatch({ 43 | type: DECREMENT 44 | }); 45 | } 46 | } 47 | 48 | export function renderReduxApp() { 49 | const reduxState = reduxStore.getState(); 50 | 51 | console.log('reduxState', reduxState); 52 | 53 | const view = ( 54 | 59 | ); 60 | 61 | ReactDOM.render(view, document.getElementById('app-redux')); 62 | } 63 | -------------------------------------------------------------------------------- /src/rx-app.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Rx from 'rx'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import type {State} from './common'; 7 | import {initState, View} from './common'; 8 | 9 | type Project = (state: State) => State; 10 | 11 | const increment$ = new Rx.Subject(); 12 | const decrement$ = new Rx.Subject(); 13 | 14 | function projectIncrement(): Project { 15 | return function (state: State): State { 16 | return Object.assign({}, state, { 17 | counter: state.counter + 1 18 | }); 19 | }; 20 | } 21 | 22 | function projectDecrement(): Project { 23 | return function (state: State): State { 24 | return Object.assign({}, state, { 25 | counter: state.counter - 1 26 | }); 27 | }; 28 | } 29 | 30 | export const rxCallbacks = { 31 | increment: function () { 32 | increment$.onNext(); 33 | }, 34 | decrement: function () { 35 | decrement$.onNext(); 36 | } 37 | }; 38 | 39 | export const rxState$ = Rx.Observable 40 | .merge( 41 | increment$.map(projectIncrement), 42 | decrement$.map(projectDecrement) 43 | ) 44 | .startWith(initState) 45 | .scan(function (state: State, project: Project) { 46 | return project(state); 47 | }); 48 | 49 | export function renderRxApp (rxState: State) { 50 | console.log('rxState', rxState); 51 | 52 | const view = ( 53 | 58 | ); 59 | 60 | ReactDOM.render(view, document.getElementById('app-rx')); 61 | } 62 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: [ 6 | './src/index' 7 | ], 8 | output: { 9 | filename: 'build/index.js' 10 | }, 11 | module: { 12 | loaders: [{ 13 | test: /\.js$/, 14 | loaders: ['babel'], 15 | exclude: /node_modules/, 16 | include: __dirname 17 | }] 18 | } 19 | }; 20 | --------------------------------------------------------------------------------