Skip to content

Commit

Permalink
Merge pull request #254 from gaearon/flow-types-take-3
Browse files Browse the repository at this point in the history
Flow types, take 3 (actually 4)
  • Loading branch information
gaearon committed Jul 19, 2015
2 parents 0623cf4 + ac8a184 commit 98ab1de
Show file tree
Hide file tree
Showing 14 changed files with 151 additions and 41 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"react/jsx-uses-vars": 2,
"react/react-in-jsx-scope": 2,

// Disable until Flow supports let and const
"no-var": 0,
"vars-on-top": 0,

//Temporarirly disabled due to a possible bug in babel-eslint (todomvc example)
"block-scoped-var": 0,
// Temporarily disabled for test/* until babel/babel-eslint#33 is resolved
Expand Down
9 changes: 9 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[ignore]
.*/lib
.*/test

[include]

[libs]

[options]
28 changes: 18 additions & 10 deletions src/createStore.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,45 @@
/* @flow */
/*eslint-disable */
import type { State, Reducer, Action, IntermediateAction, Store } from './types';
/*eslint-enable */

import invariant from 'invariant';
import isPlainObject from './utils/isPlainObject';

// Don't ever try to handle these action types in your code. They are private.
// For any unknown actions, you must return the current state.
// If the current state is undefined, you must return the initial state.
export const ActionTypes = {
export var ActionTypes = {
INIT: '@@redux/INIT'
};

export default function createStore(reducer, initialState) {
export default function createStore(
reducer: Reducer,
initialState: State
): Store {
invariant(
typeof reducer === 'function',
'Expected the reducer to be a function.'
);

let currentReducer = null;
let currentState = initialState;
let listeners = [];
var currentReducer = reducer;
var currentState = initialState;
var listeners = [];

function getState() {
return currentState;
}

function subscribe(listener) {
function subscribe(listener: Function) {
listeners.push(listener);

return function unsubscribe() {
const index = listeners.indexOf(listener);
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}

function dispatch(action) {
function dispatch(action: Action) {
invariant(
isPlainObject(action),
'Actions must be plain objects. Use custom middleware for async actions.'
Expand All @@ -46,12 +54,12 @@ export default function createStore(reducer, initialState) {
return currentReducer;
}

function replaceReducer(nextReducer) {
function replaceReducer(nextReducer: Reducer) {
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}

replaceReducer(reducer);
dispatch({ type: ActionTypes.INIT });

return {
dispatch,
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Core
/* @flow */

import createStore from './createStore';

// Utilities
import compose from './utils/compose';
import combineReducers from './utils/combineReducers';
import bindActionCreators from './utils/bindActionCreators';
Expand Down
40 changes: 40 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* @flow */

export type State = any;

export type Action = Object;

export type IntermediateAction = any;

export type Dispatch = (a: Action | IntermediateAction) => any;

export type Reducer<S, A> = (state: S, action: A) => S;

export type ActionCreator = (...args: any) =>
Action | IntermediateAction;

export type MiddlewareArgs = {
dispatch: Dispatch;
getState: () => State;
};

export type Middleware = (args: MiddlewareArgs) =>
(next: Dispatch) =>
Dispatch;

export type Store = {
dispatch: Dispatch;
getState: () => State;
getReducer: Reducer;
replaceReducer: (nextReducer: Reducer) => void;
subscribe: (listener: () => void) => () => void;
};

export type CreateStore = (
reducer: Reducer,
initialState: State
) => Store;

export type HigherOrderStore = (
next: CreateStore
) => CreateStore;
32 changes: 22 additions & 10 deletions src/utils/applyMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/* @flow */
/*eslint-disable */
import type {
Dispatch, Middleware, Reducer, State,
Store, CreateStore, HigherOrderStore
} from '../types';
/*eslint-enable */

import compose from './compose';
import composeMiddleware from './composeMiddleware';

Expand All @@ -8,23 +16,27 @@ import composeMiddleware from './composeMiddleware';
* @param {...Function} ...middlewares
* @return {Function} A higher-order store
*/
export default function applyMiddleware(...middlewares) {
return next => (...args) => {
const store = next(...args);
const middleware = composeMiddleware(...middlewares);

let composedDispatch = null;
export default function applyMiddleware(
...middlewares: Array<Middleware>
): HigherOrderStore {
return (next: CreateStore) => (reducer: Reducer, initialState: State) => {
var store = next(reducer, initialState);
var middleware = composeMiddleware(...middlewares);
var composedDispatch = () => {};

function dispatch(action) {
return composedDispatch(action);
}

const methods = {
dispatch,
getState: store.getState
var middlewareAPI = {
getState: store.getState,
dispatch
};

composedDispatch = compose(middleware(methods), store.dispatch);
composedDispatch = compose(
middleware(middlewareAPI),
store.dispatch
);

return {
...store,
Expand Down
10 changes: 9 additions & 1 deletion src/utils/bindActionCreators.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
/* @flow */
/*eslint-disable */
import type { Dispatch } from '../types';
/*eslint-enable */

import mapValues from '../utils/mapValues';

export default function bindActionCreators(actionCreators, dispatch) {
export default function bindActionCreators(
actionCreators: Object,
dispatch: Dispatch
): Object {
return mapValues(actionCreators, actionCreator =>
(...args) => dispatch(actionCreator(...args))
);
Expand Down
23 changes: 14 additions & 9 deletions src/utils/combineReducers.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
/* @flow */
/*eslint-disable */
import type { Action, State, Reducer } from '../types';
/*eslint-enable */

import mapValues from '../utils/mapValues';
import pick from '../utils/pick';
import invariant from 'invariant';
import { ActionTypes } from '../createStore';

function getErrorMessage(key, action) {
const actionType = action && action.type;
const actionName = actionType && `"${actionType}"` || 'an action';
function getErrorMessage(key: String, action: Action): string {
var actionType = action && action.type;
var actionName = actionType && `"${actionType}"` || 'an action';

return (
`Reducer "${key}" returned undefined handling ${actionName}. ` +
`To ignore an action, you must explicitly return the previous state.`
);
}

export default function combineReducers(reducers) {
const finalReducers = pick(reducers, (val) => typeof val === 'function');
export default function combineReducers(reducers: Object): Reducer {
var finalReducers = pick(reducers, (val) => typeof val === 'function');

Object.keys(finalReducers).forEach(key => {
const reducer = finalReducers[key];
var reducer = finalReducers[key];
invariant(
typeof reducer(undefined, { type: ActionTypes.INIT }) !== 'undefined',
`Reducer "${key}" returned undefined during initialization. ` +
Expand All @@ -26,7 +31,7 @@ export default function combineReducers(reducers) {
`not be undefined.`
);

const type = Math.random().toString(36).substring(7).split('').join('.');
var type = Math.random().toString(36).substring(7).split('').join('.');
invariant(
typeof reducer(undefined, { type }) !== 'undefined',
`Reducer "${key}" returned undefined when probed with a random type. ` +
Expand All @@ -38,9 +43,9 @@ export default function combineReducers(reducers) {
);
});

return function composition(state = {}, action) {
return function composition(state: State = {}, action: Action): State {
return mapValues(finalReducers, (reducer, key) => {
const newState = reducer(state[key], action);
var newState = reducer(state[key], action);
invariant(
typeof newState !== 'undefined',
getErrorMessage(key, action)
Expand Down
4 changes: 3 additions & 1 deletion src/utils/compose.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* @flow */

/**
* Composes functions from left to right
* @param {...Function} funcs - Functions to compose
* @return {Function}
*/
export default function compose(...funcs) {
export default function compose(...funcs: Array<Function>): Function {
return funcs.reduceRight((composed, f) => f(composed));
}
15 changes: 13 additions & 2 deletions src/utils/composeMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
/* @flow */
/*eslint-disable */
import type { Dispatch, Middleware, MiddlewareArgs } from '../types';
/*eslint-enable */

import compose from './compose';

/**
* Compose middleware from left to right
* @param {...Function} middlewares
* @return {Function}
*/
export default function composeMiddleware(...middlewares) {
return methods => next => compose(...middlewares.map(m => m(methods)), next);
export default function composeMiddleware(
...middlewares: Array<Middleware>
): Middleware {
return (args: MiddlewareArgs) => (next: Dispatch) => {
var dispatchChain = middlewares.map(middleware => middleware(args));
dispatchChain.push(next);
return compose.apply(null, dispatchChain);
};
}
11 changes: 9 additions & 2 deletions src/utils/isPlainObject.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
export default function isPlainObject(obj) {
return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false;
/* @flow */

export default function isPlainObject(obj: Object): boolean {
if (!obj) {
return false;
}

return typeof obj === 'object' &&
Object.getPrototypeOf(obj) === Object.prototype;
}
4 changes: 3 additions & 1 deletion src/utils/mapValues.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default function mapValues(obj, fn) {
/* @flow */

export default function mapValues(obj: Object, fn: Function): Object {
return Object.keys(obj).reduce((result, key) => {
result[key] = fn(obj[key], key);
return result;
Expand Down
4 changes: 3 additions & 1 deletion src/utils/pick.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default function pick(obj, fn) {
/* @flow */

export default function pick(obj: Object, fn: Function): Object {
return Object.keys(obj).reduce((result, key) => {
if (fn(obj[key])) {
result[key] = obj[key];
Expand Down
4 changes: 2 additions & 2 deletions test/utils/applyMiddleware.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ describe('applyMiddleware', () => {
expect(spy.calls.length).toEqual(1);

expect(Object.keys(spy.calls[0].arguments[0])).toEqual([
'dispatch',
'getState'
'getState',
'dispatch'
]);

expect(store.getState()).toEqual([ { id: 1, text: 'Use Redux' }, { id: 2, text: 'Flux FTW!' } ]);
Expand Down

0 comments on commit 98ab1de

Please sign in to comment.