Tweaks to flow types

This commit is contained in:
Kai Moseley 2016-12-05 21:21:15 +00:00
parent 69a0d3c616
commit 00087c751d
7 changed files with 164 additions and 35 deletions

View File

@ -1,5 +1,5 @@
import { buildReducers } from './redux-arg/buildReducers';
import { createStore, compose } from 'redux';
import { createStore, compose, combineReducers } from 'redux';
import { Types } from './redux-arg/structure';
const exampleReducer = {
@ -30,26 +30,62 @@ const exampleReducer = {
arrayTest: Types.reducer(Types.arrayOf(
Types.number(),
[1,3,4]
))
)),
primitiveTest: Types.reducer(Types.number(4)),
})
};
const exampleReducer2 = {
screen: Types.reducer(Types.shape({
handlerIsCBS: Types.boolean(true),
handlerIsENG: Types.boolean(true),
invoices: Types.arrayOf(Types.string()),
activeInvoice: Types.number(-1),
deleteInvoiceModalActive: Types.boolean(),
suppInvoiceModalActive: Types.boolean(),
paymentModalActive: Types.boolean(),
resetModalActive: Types.boolean(),
reinstateModalActive: Types.boolean(),
})),
delete: Types.reducer(Types.shape({
invoiceSequence: Types.number(),
vehicleSequence: Types.number(),
deleteReason: Types.string(),
})),
supp: Types.reducer(Types.shape({
invoiceSequence: Types.number(),
vehicleSequence: Types.number(),
mode: Types.string(),
amount: Types.string(),
})),
};
const test = buildReducers('example', exampleReducer);
const test2 = buildReducers('invoices', exampleReducer2);
const store = createStore(
test.reducers,
combineReducers({
example: test.reducers,
invoices: test2.reducers,
}),
compose(window.devToolsExtension ? window.devToolsExtension() : f => f)
);
store.dispatch(test.actionsObject.example.form2.update({ lowerLevel: 2, lowerLevel2: 'Rawrg', lowerLevelArray: [3, 'foo'] }));
store.dispatch(test.actionsObject.example.form2.reset());
store.dispatch(test.actionsObject.example.form2.replace({ toast: 'nommyNom' }));
store.dispatch(test.actionsObject.example.form2.reset());
store.dispatch(test.actions.example.form2.update({ lowerLevel: 2, lowerLevel2: 'Rawrg', lowerLevelArray: [3, 'foo'] }));
store.dispatch(test.actions.example.form2.reset());
store.dispatch(test.actions.example.form2.replace({ toast: 'nommyNom' }));
store.dispatch(test.actions.example.form2.reset());
console.log(111, test.selectorsObject.example.form2(store.getState()));
console.log(222, test.actionsObject);
console.log(111, test.selectors.example.form2(store.getState()));
console.log(222, test.actions);
console.log(333, test.selectors);
console.log(444, test.selectors.example.primitiveTest(store.getState()));
console.log(555, test2.selectors, test2.actions);
console.log(666, test2.selectors.screen(store.getState()));
store.dispatch(test.actionsObject.example.arrayTest.replace([1,2,3]));
store.dispatch(test.actionsObject.example.arrayTest.updateAtIndex(5, 0));
store.dispatch(test.actionsObject.example.arrayTest.updateAtIndex('foo', 0));
store.dispatch(test.actions.example.arrayTest.replace([1,2,3]));
store.dispatch(test.actions.example.arrayTest.updateAtIndex(5, 0));
store.dispatch(test.actions.example.arrayTest.updateAtIndex('foo', 0));
store.dispatch(test.actions.example.primitiveTest.update(5));
store.dispatch(test.actions.example.primitiveTest.reset());

View File

@ -10,8 +10,9 @@ import { reduce, find } from 'lodash';
import { createReducer } from './reducers';
import { PROP_TYPES } from './structure';
export function buildReducers(name: string, structure: any, {
baseSelector = state => state,
baseSelector = state => state[name],
locationString = '',
}: {
baseSelector: any,
@ -24,8 +25,8 @@ export function buildReducers(name: string, structure: any, {
//returned to the call site for use in the rest of the application.
const temp = reduce(structure, processStructure, {
reducers: {},
actionsObject: {},
selectorsObject: {},
actions: {},
selectors: {},
});
//The Redux 'combineReducers' helper function is used here to save a little bit of boilerplate.
@ -63,13 +64,13 @@ export function buildReducers(name: string, structure: any, {
...memo.reducers,
[propName]: childReducer.reducers,
},
actionsObject: {
...memo.actionsObject,
[propName]: childReducer.actionsObject,
actions: {
...memo.actions,
[propName]: childReducer.actions,
},
selectorsObject: {
...memo.selectorsObject,
[propName]: containsReducers ? childReducer.selectorsObject : state => baseSelector(state)[propName],
selectors: {
...memo.selectors,
[propName]: containsReducers ? childReducer.selectors : state => baseSelector(state)[propName],
},
};
}

View File

@ -12,10 +12,12 @@ import type {
//==============================
export type PartialReducer = {
reducers: { [key: string]: any },
actionsObject: { [key: string]: any },
selectorsObject?: { [key: string]: any },
actions: { [key: string]: any },
selectors: { [key: string]: any },
};
export type Selector = (state: Object) => any;
import {
PROP_TYPES,
} from './structure';
@ -23,6 +25,7 @@ import { compose } from 'ramda';
import { reduce } from 'lodash';
import { createObjectReducer } from './reducers/objectReducer';
import { createArrayReducer } from './reducers/arrayReducer';
import { createPrimitiveReducer } from './reducers/primitiveReducer';
function determineReducerType(reducerDescriptor, {
@ -31,9 +34,9 @@ function determineReducerType(reducerDescriptor, {
const REDUCERS = {
[PROP_TYPES._shape]: createObjectReducer,
[PROP_TYPES._array]: createArrayReducer,
[PROP_TYPES._boolean]: () => {},
[PROP_TYPES._string]: () => {},
[PROP_TYPES._number]: () => {},
[PROP_TYPES._boolean]: createPrimitiveReducer,
[PROP_TYPES._string]: createPrimitiveReducer,
[PROP_TYPES._number]: createPrimitiveReducer,
};
const { structure } = reducerDescriptor();
const { type } = structure();

View File

@ -2,7 +2,7 @@
//==============================
// Flow imports
//==============================
import type { StructureType, ArrayStructureType } from '../structure';
import type { ArrayStructureType } from '../structure';
//==============================
// Flow types
@ -100,7 +100,7 @@ export function createArrayReducer(arrayTypeDescription: ArrayStructureType, {
}: ArrayReducerOptions = {}) {
return {
reducers: createReducer(arrayTypeDescription, createReducerBehaviors(DEFAULT_ARRAY_BEHAVIORS, locationString)),
actionsObject: createActions(DEFAULT_ARRAY_BEHAVIORS, locationString, {}),
actions: createActions(DEFAULT_ARRAY_BEHAVIORS, locationString, {}),
};
}
@ -146,7 +146,7 @@ function createActions(behaviorsConfig: ArrayReducerBehaviorsConfig, locationStr
...memo,
[name]: (value: Array<any>, index: ?number) => ({
type: `${locationString}.${name}`,
payload: (behavior.action || (() => value))(value) || [],
payload: (behavior.action || (() => value))(value),
index,
})
}), {});

View File

@ -2,7 +2,7 @@
//==============================
// Flow imports
//==============================
import type { ShapeStructure, StructureType } from '../structure';
import type { StructureType } from '../structure';
//==============================
// Flow types
@ -11,7 +11,6 @@ export type ObjectReducerAction = {
type: string,
payload: Object,
};
export type ObjectReducerFactory = (reducerStructure: ShapeStructure) => ObjectReducer;
export type ObjectReducer = (state: Object, action: ObjectReducerAction) => Object;
export type ObjectReducerBehavior = (state: Object, payload: Object | void, initialState: Object) => Object;
export type ObjectReducerBehaviorsConfig = {
@ -31,7 +30,6 @@ export type ObjectReducerOptions = {
behaviorsConfig: ObjectReducerBehaviorsConfig,
locationString: string,
};
export type ObjectSelector = (state: Object) => Object;
//==============================
// JS imports
@ -66,7 +64,7 @@ export function createObjectReducer(reducerShape: StructureType, {
}: ObjectReducerOptions = {}) {
return {
reducers: createReducer(reducerShape, createReducerBehaviors(DEFAULT_OBJECT_BEHAVIORS, locationString)),
actionsObject: createActions(DEFAULT_OBJECT_BEHAVIORS, locationString, {}),
actions: createActions(DEFAULT_OBJECT_BEHAVIORS, locationString, {}),
};
}
@ -100,7 +98,7 @@ function createActions(behaviorsConfig: ObjectReducerBehaviorsConfig, locationSt
...memo,
[name]: (value: Object) => ({
type: `${locationString}.${name}`,
payload: (behavior.action || (() => defaultPayload))(value) || {}
payload: (behavior.action || (() => defaultPayload))(value),
})
}), {});
}

View File

@ -1 +1,92 @@
//@flow
//==============================
// Flow imports
//==============================
import type { PrimitiveType } from '../structure';
//==============================
// Flow types
//==============================
export type PrimitiveReducerAction = {
type: string,
payload: mixed,
};
export type PrimitiveReducer = (state: mixed, action: PrimitiveReducerAction) => mixed;
export type PrimitiveReducerBehavior = (state: mixed, payload: mixed | void, initialState: mixed) => mixed;
export type PrimitiveReducerBehaviorsConfig = {
[key: string]: {
action?: (value: mixed) => mixed,
reducer: PrimitiveReducerBehavior,
}
};
export type PrimitiveReducerBehaviors = {
[key: string]: PrimitiveReducerBehavior,
};
export type PrimitiveAction = (value: mixed) => { type: string, payload: mixed };
export type PrimitiveActions = {
[key: string]: PrimitiveAction
};
export type PrimitiveReducerOptions = {
behaviorsConfig: PrimitiveReducerBehaviorsConfig,
locationString: string,
};
//==============================
// JS imports
//==============================
import { reduce } from 'lodash';
import { validatePrimitive } from '../validatePayload';
import { createReducerBehaviors } from '../reducers';
const DEFAULT_PRIMITIVE_BEHAVIORS: PrimitiveReducerBehaviorsConfig = {
update: {
action(value) { return value },
reducer(state, payload) {
if (payload === undefined) return state;
return payload;
}
},
reset: {
reducer(state, payload, initialState) {
return initialState;
}
},
};
export function createPrimitiveReducer(primitiveType: PrimitiveType, {
locationString
}: PrimitiveReducerOptions = {}) {
return {
reducers: createReducer(primitiveType, createReducerBehaviors(DEFAULT_PRIMITIVE_BEHAVIORS, locationString)),
actions: createActions(DEFAULT_PRIMITIVE_BEHAVIORS, locationString, {}),
};
}
function createReducer(primitiveType: PrimitiveType, behaviors: PrimitiveReducerBehaviors): PrimitiveReducer {
const initialState: mixed = validatePrimitive(primitiveType, primitiveType().defaultValue);
return (state = initialState, { type, payload }: PrimitiveReducerAction) => {
//If the action type does not match any of the specified behaviors, just return the current state.
if (!behaviors[type]) return state;
//Sanitize the payload using the reducer shape, then apply the sanitized
//payload to the state using the behavior linked to this action type.
return behaviors[type](state, validatePrimitive(primitiveType, payload), initialState);
}
}
function createActions(behaviorsConfig: PrimitiveReducerBehaviorsConfig, locationString: string, defaultPayload: any): PrimitiveActions {
//Take a reducer behavior config object, and create actions using the location string
return reduce(behaviorsConfig, (memo, behavior, name) => ({
...memo,
[name]: (value: mixed) => ({
type: `${locationString}.${name}`,
payload: (behavior.action || (() => defaultPayload))(value),
})
}), {});
}

View File

@ -4,7 +4,7 @@
// Flow types
//==============================
export type ShapeStructure = {
[key: string]: StructureType | PrimitiveType,
[key: string]: StructureType | PrimitiveType | ArrayStructureType,
}
export type StructureType = () => {
type: string,