Skip to content

Commit

Permalink
Add Flow type annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite committed Jul 10, 2015
1 parent 5d5dbcc commit 4c68696
Show file tree
Hide file tree
Showing 17 changed files with 121 additions and 49 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]
26 changes: 17 additions & 9 deletions src/Store.js
Original file line number Diff line number Diff line change
@@ -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<Function>;

constructor(reducer: Reducer, initialState: State): void {
invariant(
typeof reducer === 'function',
'Expected the reducer to be a function.'
Expand All @@ -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);
};
}
Expand Down
26 changes: 15 additions & 11 deletions src/createStore.js
Original file line number Diff line number Diff line change
@@ -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)
};
}
11 changes: 9 additions & 2 deletions src/middleware/thunk.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
10 changes: 10 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
@@ -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<S, A> = (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;
16 changes: 11 additions & 5 deletions src/utils/applyMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
/* @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
* higher-order store in the composition chain.
* @param {...Function} ...middlewares
* @return {Function} A higher-order store
*/
export default function applyMiddleware(...middlewares) {
const finalMiddlewares = middlewares.length ?
export default function applyMiddleware(
...middlewares: Array<Middleware>
): 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
};
Expand Down
8 changes: 7 additions & 1 deletion src/utils/bindActionCreators.js
Original file line number Diff line number Diff line change
@@ -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))
);
Expand Down
8 changes: 5 additions & 3 deletions src/utils/combineReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}"`;
Expand All @@ -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(
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));
}
4 changes: 3 additions & 1 deletion src/utils/createStoreShape.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
4 changes: 3 additions & 1 deletion src/utils/identity.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export default function identity(value) {
/* @flow */

export default function identity<T>(value: T): T {
return value;
}
4 changes: 3 additions & 1 deletion src/utils/isPlainObject.js
Original file line number Diff line number Diff line change
@@ -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;
}
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
12 changes: 7 additions & 5 deletions src/utils/shallowEqual.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
16 changes: 9 additions & 7 deletions src/utils/shallowEqualScalar.js
Original file line number Diff line number Diff line change
@@ -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;
}
Expand All @@ -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;
Expand Down

0 comments on commit 4c68696

Please sign in to comment.