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 { buildReducers } from './redux-arg/buildReducers';
import { createStore, compose } from 'redux'; import { createStore, compose, combineReducers } from 'redux';
import { Types } from './redux-arg/structure'; import { Types } from './redux-arg/structure';
const exampleReducer = { const exampleReducer = {
@ -30,26 +30,62 @@ const exampleReducer = {
arrayTest: Types.reducer(Types.arrayOf( arrayTest: Types.reducer(Types.arrayOf(
Types.number(), Types.number(),
[1,3,4] [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 test = buildReducers('example', exampleReducer);
const test2 = buildReducers('invoices', exampleReducer2);
const store = createStore( const store = createStore(
test.reducers, combineReducers({
example: test.reducers,
invoices: test2.reducers,
}),
compose(window.devToolsExtension ? window.devToolsExtension() : f => f) compose(window.devToolsExtension ? window.devToolsExtension() : f => f)
); );
store.dispatch(test.actionsObject.example.form2.update({ lowerLevel: 2, lowerLevel2: 'Rawrg', lowerLevelArray: [3, 'foo'] })); store.dispatch(test.actions.example.form2.update({ lowerLevel: 2, lowerLevel2: 'Rawrg', lowerLevelArray: [3, 'foo'] }));
store.dispatch(test.actionsObject.example.form2.reset()); store.dispatch(test.actions.example.form2.reset());
store.dispatch(test.actionsObject.example.form2.replace({ toast: 'nommyNom' })); store.dispatch(test.actions.example.form2.replace({ toast: 'nommyNom' }));
store.dispatch(test.actionsObject.example.form2.reset()); store.dispatch(test.actions.example.form2.reset());
console.log(111, test.selectorsObject.example.form2(store.getState())); console.log(111, test.selectors.example.form2(store.getState()));
console.log(222, test.actionsObject); 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.actions.example.arrayTest.replace([1,2,3]));
store.dispatch(test.actionsObject.example.arrayTest.updateAtIndex(5, 0)); store.dispatch(test.actions.example.arrayTest.updateAtIndex(5, 0));
store.dispatch(test.actionsObject.example.arrayTest.updateAtIndex('foo', 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 { createReducer } from './reducers';
import { PROP_TYPES } from './structure'; import { PROP_TYPES } from './structure';
export function buildReducers(name: string, structure: any, { export function buildReducers(name: string, structure: any, {
baseSelector = state => state, baseSelector = state => state[name],
locationString = '', locationString = '',
}: { }: {
baseSelector: any, 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. //returned to the call site for use in the rest of the application.
const temp = reduce(structure, processStructure, { const temp = reduce(structure, processStructure, {
reducers: {}, reducers: {},
actionsObject: {}, actions: {},
selectorsObject: {}, selectors: {},
}); });
//The Redux 'combineReducers' helper function is used here to save a little bit of boilerplate. //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, ...memo.reducers,
[propName]: childReducer.reducers, [propName]: childReducer.reducers,
}, },
actionsObject: { actions: {
...memo.actionsObject, ...memo.actions,
[propName]: childReducer.actionsObject, [propName]: childReducer.actions,
}, },
selectorsObject: { selectors: {
...memo.selectorsObject, ...memo.selectors,
[propName]: containsReducers ? childReducer.selectorsObject : state => baseSelector(state)[propName], [propName]: containsReducers ? childReducer.selectors : state => baseSelector(state)[propName],
}, },
}; };
} }

View File

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

View File

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

View File

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