├── .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 |
--------------------------------------------------------------------------------