From 4c68696134f48a1ad71f62ce9aae3031c69ac0c5 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Jul 2015 23:41:47 -0700 Subject: [PATCH] Add Flow type annotations --- .eslintrc | 4 ++++ .flowconfig | 9 +++++++++ src/Store.js | 26 +++++++++++++++++--------- src/createStore.js | 26 +++++++++++++++----------- src/middleware/thunk.js | 11 +++++++++-- src/types.js | 10 ++++++++++ src/utils/applyMiddleware.js | 16 +++++++++++----- src/utils/bindActionCreators.js | 8 +++++++- src/utils/combineReducers.js | 8 +++++--- src/utils/compose.js | 4 +++- src/utils/createStoreShape.js | 4 +++- src/utils/identity.js | 4 +++- src/utils/isPlainObject.js | 4 +++- src/utils/mapValues.js | 4 +++- src/utils/pick.js | 4 +++- src/utils/shallowEqual.js | 12 +++++++----- src/utils/shallowEqualScalar.js | 16 +++++++++------- 17 files changed, 121 insertions(+), 49 deletions(-) create mode 100644 .flowconfig create mode 100644 src/types.js diff --git a/.eslintrc b/.eslintrc index 5cf245ac59..90e3903312 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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 diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000000..369768b7ad --- /dev/null +++ b/.flowconfig @@ -0,0 +1,9 @@ +[ignore] +.*/lib +.*/test + +[include] + +[libs] + +[options] diff --git a/src/Store.js b/src/Store.js index ed85b987fc..5ee17c431d 100644 --- a/src/Store.js +++ b/src/Store.js @@ -1,8 +1,16 @@ +/* @flow */ + import invariant from 'invariant'; import isPlainObject from './utils/isPlainObject'; +import type { State, Action, Reducer } from './types'; + export default class Store { - constructor(reducer, initialState) { + state: State; + reducer: Reducer; + listeners: Array; + + constructor(reducer: Reducer, initialState: State): void { invariant( typeof reducer === 'function', 'Expected the reducer to be a function.' @@ -13,37 +21,37 @@ export default class Store { this.replaceReducer(reducer); } - getReducer() { + getReducer(): Reducer { return this.reducer; } - replaceReducer(nextReducer) { + replaceReducer(nextReducer: Reducer): void { this.reducer = nextReducer; this.dispatch({ type: '@@INIT' }); } - dispatch(action) { + dispatch(action: Action): Action { invariant( isPlainObject(action), 'Actions must be plain objects. Use custom middleware for async actions.' ); - const { reducer } = this; + var { reducer } = this; this.state = reducer(this.state, action); this.listeners.forEach(listener => listener()); return action; } - getState() { + getState(): State { return this.state; } - subscribe(listener) { - const { listeners } = this; + subscribe(listener: Function): Function { + var { listeners } = this; listeners.push(listener); return function unsubscribe() { - const index = listeners.indexOf(listener); + var index = listeners.indexOf(listener); listeners.splice(index, 1); }; } diff --git a/src/createStore.js b/src/createStore.js index 6a79406c98..1bfca57b2f 100644 --- a/src/createStore.js +++ b/src/createStore.js @@ -1,21 +1,25 @@ -import Store from './Store'; +/* @flow */ + +import StoreClass from './Store'; import combineReducers from './utils/combineReducers'; +import type { State, Action, Reducer, Dispatch, Store } from './types'; + export default function createStore( - reducer, - initialState -) { - const finalReducer = typeof reducer === 'function' ? + reducer: Reducer, + initialState: State +): Store { + var finalReducer = typeof reducer === 'function' ? reducer : combineReducers(reducer); - const store = new Store(finalReducer, initialState); + var store = new StoreClass(finalReducer, initialState); return { - dispatch: ::store.dispatch, - subscribe: ::store.subscribe, - getState: ::store.getState, - getReducer: ::store.getReducer, - replaceReducer: ::store.replaceReducer + dispatch: store.dispatch.bind(store), + subscribe: store.subscribe.bind(store), + getState: store.getState.bind(store), + getReducer: store.getReducer.bind(store), + replaceReducer: store.replaceReducer.bind(store) }; } diff --git a/src/middleware/thunk.js b/src/middleware/thunk.js index e6638cdb06..121aaa9bc9 100644 --- a/src/middleware/thunk.js +++ b/src/middleware/thunk.js @@ -1,5 +1,12 @@ -export default function thunkMiddleware({ dispatch, getState }) { - return next => action => +/* @flow */ + +import type { Dispatch, State, Action } from '../types'; + +type StoreMethods = { dispatch: Dispatch, getState: () => State }; + +export default function thunkMiddleware(storeMethods: StoreMethods): Dispatch { + var { dispatch, getState } = storeMethods; + return (next: Dispatch) => (action: Action) => typeof action === 'function' ? action(dispatch, getState) : next(action); diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000000..e1b79270d1 --- /dev/null +++ b/src/types.js @@ -0,0 +1,10 @@ +export type State = any; +export type Action = Object; +export type IntermediateAction = any; +export type Dispatch = (a: Action | IntermediateAction) => any; +export type Reducer = (state: S, action: A) => S; +export type ActionCreator = (...args: any) => Action | IntermediateAction +export type Middleware = (methods: { dispatch: Dispatch, getState: () => State }) => (next: Dispatch) => Dispatch; +export type Store = { dispatch: Dispatch, getState: State, subscribe: Function, getReducer: Reducer, replaceReducer: void }; +export type CreateStore = (reducer: Function, initialState: any) => Store; +export type HigherOrderStore = (next: CreateStore) => CreateStore; diff --git a/src/utils/applyMiddleware.js b/src/utils/applyMiddleware.js index e6c63ad0bb..a310afd63b 100644 --- a/src/utils/applyMiddleware.js +++ b/src/utils/applyMiddleware.js @@ -1,7 +1,11 @@ +/* @flow */ + import compose from './compose'; import composeMiddleware from './composeMiddleware'; import thunk from '../middleware/thunk'; +import type { Middleware, Dispatch, CreateStore } from '../types'; + /** * Creates a higher-order store that applies middleware to a store's dispatch. * Because middleware is potentially asynchronous, this should be the first @@ -9,14 +13,16 @@ import thunk from '../middleware/thunk'; * @param {...Function} ...middlewares * @return {Function} A higher-order store */ -export default function applyMiddleware(...middlewares) { - const finalMiddlewares = middlewares.length ? +export default function applyMiddleware( + ...middlewares: Array +): Dispatch { + var finalMiddlewares = middlewares.length ? middlewares : [thunk]; - return next => (...args) => { - const store = next(...args); - const methods = { + return (next: CreateStore) => (...args) => { + var store = next(...args); + var methods = { dispatch: store.dispatch, getState: store.getState }; diff --git a/src/utils/bindActionCreators.js b/src/utils/bindActionCreators.js index f9a81a5a80..05374c812c 100644 --- a/src/utils/bindActionCreators.js +++ b/src/utils/bindActionCreators.js @@ -1,6 +1,12 @@ +/* @flow */ + import mapValues from '../utils/mapValues'; -export default function bindActionCreators(actionCreators, dispatch) { +import type { Dispatch } from '../types'; + +export default function bindActionCreators( + actionCreators: Object, dispatch: Dispatch +): Object { return mapValues(actionCreators, actionCreator => (...args) => dispatch(actionCreator(...args)) ); diff --git a/src/utils/combineReducers.js b/src/utils/combineReducers.js index fe65c056cc..600e074e08 100644 --- a/src/utils/combineReducers.js +++ b/src/utils/combineReducers.js @@ -2,7 +2,9 @@ import mapValues from '../utils/mapValues'; import pick from '../utils/pick'; import invariant from 'invariant'; -function getErrorMessage(key, action) { +import type { Action, State, Reducer } from '../types'; + +function getErrorMessage(key: String, action: Action): String { const actionType = action && action.type; const actionName = actionType && `"${actionType}"` || 'an action'; const reducerName = `Reducer "${key}"`; @@ -21,10 +23,10 @@ function getErrorMessage(key, action) { ); } -export default function combineReducers(reducers) { +export default function combineReducers(reducers: Object): Reducer { const finalReducers = pick(reducers, (val) => typeof val === 'function'); - return function composition(state = {}, action) { + return function composition(state: State = {}, action: Action): State { return mapValues(finalReducers, (reducer, key) => { const newState = reducer(state[key], action); invariant( diff --git a/src/utils/compose.js b/src/utils/compose.js index 4db0884d3b..b4b291e015 100644 --- a/src/utils/compose.js +++ b/src/utils/compose.js @@ -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 { return funcs.reduceRight((composed, f) => f(composed)); } diff --git a/src/utils/createStoreShape.js b/src/utils/createStoreShape.js index 851e7ce898..f4e22159ed 100644 --- a/src/utils/createStoreShape.js +++ b/src/utils/createStoreShape.js @@ -1,4 +1,6 @@ -export default function createStoreShape(PropTypes) { +/* @flow */ + +export default function createStoreShape(PropTypes: Object): Object { return PropTypes.shape({ subscribe: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired, diff --git a/src/utils/identity.js b/src/utils/identity.js index 8c690a8859..ccab53fd9a 100644 --- a/src/utils/identity.js +++ b/src/utils/identity.js @@ -1,3 +1,5 @@ -export default function identity(value) { +/* @flow */ + +export default function identity(value: T): T { return value; } diff --git a/src/utils/isPlainObject.js b/src/utils/isPlainObject.js index a5845486cf..b31d8f37a3 100644 --- a/src/utils/isPlainObject.js +++ b/src/utils/isPlainObject.js @@ -1,3 +1,5 @@ -export default function isPlainObject(obj) { +/* @flow */ + +export default function isPlainObject(obj: Object): boolean { return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false; } diff --git a/src/utils/mapValues.js b/src/utils/mapValues.js index 29d203cf61..9e09aeaab8 100644 --- a/src/utils/mapValues.js +++ b/src/utils/mapValues.js @@ -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; diff --git a/src/utils/pick.js b/src/utils/pick.js index 2c9719c1c0..7025cb35e3 100644 --- a/src/utils/pick.js +++ b/src/utils/pick.js @@ -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]; diff --git a/src/utils/shallowEqual.js b/src/utils/shallowEqual.js index f82be71949..8414ec3be1 100644 --- a/src/utils/shallowEqual.js +++ b/src/utils/shallowEqual.js @@ -1,18 +1,20 @@ -export default function shallowEqual(objA, objB) { +/* @flow */ + +export default function shallowEqual(objA: Object, objB: Object): boolean { if (objA === objB) { return true; } - const keysA = Object.keys(objA); - const keysB = Object.keys(objB); + var keysA = Object.keys(objA); + var keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. - const hasOwn = Object.prototype.hasOwnProperty; - for (let i = 0; i < keysA.length; i++) { + var hasOwn = Object.prototype.hasOwnProperty; + for (var i = 0; i < keysA.length; i++) { if (!hasOwn.call(objB, keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) { return false; diff --git a/src/utils/shallowEqualScalar.js b/src/utils/shallowEqualScalar.js index 2adb8ea85b..8f4f779a03 100644 --- a/src/utils/shallowEqualScalar.js +++ b/src/utils/shallowEqualScalar.js @@ -1,4 +1,6 @@ -export default function shallowEqualScalar(objA, objB) { +/* @flow */ + +export default function shallowEqualScalar(objA: Object, objB: Object): boolean { if (objA === objB) { return true; } @@ -8,22 +10,22 @@ export default function shallowEqualScalar(objA, objB) { return false; } - const keysA = Object.keys(objA); - const keysB = Object.keys(objB); + var keysA = Object.keys(objA); + var keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. - const hasOwn = Object.prototype.hasOwnProperty; - for (let i = 0; i < keysA.length; i++) { + var hasOwn = Object.prototype.hasOwnProperty; + for (var i = 0; i < keysA.length; i++) { if (!hasOwn.call(objB, keysA[i])) { return false; } - const valA = objA[keysA[i]]; - const valB = objB[keysA[i]]; + var valA = objA[keysA[i]]; + var valB = objB[keysA[i]]; if (valA !== valB || typeof valA === 'object' || typeof valB === 'object') { return false;