diff --git a/playground/src/actions/index.ts b/playground/src/actions/index.ts index 3fa900d..d158d2d 100644 --- a/playground/src/actions/index.ts +++ b/playground/src/actions/index.ts @@ -1,15 +1,23 @@ -import { Action, ActionCreator } from 'redux'; +import { Action } from 'redux'; -export type ActionTypes = 'add' | 'remove' | 'edit' | 'complete' | 'completeAll' | 'clear' | 'filter'; +type ActionWithPayload = Action & P; -export interface TodoAction extends Action { - [extraProps: string]: any; +function action(type: T): Action; +function action(type: T, payload: P): ActionWithPayload; +function action(type: T, payload?: P) { + return { type, ...payload }; } -export const add = (label: string): TodoAction => ({ type: 'add', label }); -export const remove = (id: string): TodoAction => ({ type: 'remove', id }); -export const edit = (id: string, label: string): TodoAction => ({ type: 'edit', id, label }); -export const complete = (id: string): TodoAction => ({ type: 'complete', id }); -export const completeAll = (): TodoAction => ({ type: 'completeAll' }); -export const clear = (): TodoAction => ({ type: 'clear' }); -export const filter = (filterTypes: string): TodoAction => ({ type: 'filter', filter: filterTypes }); +export const add = (label: string) => action('add', { label }); +export const remove = (id: string) => ({ type: 'remove' as 'remove', id }); +export const edit = (id: string, label: string) => ({ type: 'edit' as 'edit', id, label }); +export const complete = (id: string) => ({ type: 'complete' as 'complete', id }); +export const completeAll = () => ({ type: 'completeAll' as 'completeAll' }); +export const clear = () => ({ type: 'clear' as 'clear' }); +export const filter = (filterTypes: string) => ({ type: 'filter' as 'filter', filter: filterTypes }); + +export const actions = { add, remove, edit, complete, completeAll, clear, filter }; + +export type ActionTypes = ReturnType['type']; +export type TodoAction = ReturnType; +export type TodoActionLookup = { [a in ActionTypes]: ReturnType }; diff --git a/playground/src/components/TodoAppContainer.tsx b/playground/src/components/TodoAppContainer.tsx index 58b12c0..837922a 100644 --- a/playground/src/components/TodoAppContainer.tsx +++ b/playground/src/components/TodoAppContainer.tsx @@ -1,4 +1,4 @@ -import * as actions from '../actions'; +import { actions, TodoAction } from '../actions'; import { Store, FilterTypes } from '../store'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; @@ -11,7 +11,7 @@ export function mapStateToProps({ todos, filter }: Store) { }; } -export function mapDispatchToProps(dispatch: Dispatch) { +export function mapDispatchToProps(dispatch: Dispatch) { return { add: (label: string) => dispatch(actions.add(label)), remove: (id: string) => dispatch(actions.remove(id)), diff --git a/playground/src/components/TodoList.tsx b/playground/src/components/TodoList.tsx index 357ba2d..01e2a8f 100644 --- a/playground/src/components/TodoList.tsx +++ b/playground/src/components/TodoList.tsx @@ -14,21 +14,27 @@ export interface TodoListProps { export class TodoList extends React.Component { render() { const { filter, todos } = this.props; - let filteredTodos = todos; + let filteredTodos: typeof todos = {}; switch (filter) { case 'completed': - filteredTodos = Object.keys(todos).reduce( - (collection, id) => (todos[id].completed ? { ...collection, id: todos[id] } : collection), - {} - ); + Object.keys(todos).forEach(id => { + if (todos[id].completed) { + filteredTodos[id] = todos[id]; + } + }); break; case 'active': - filteredTodos = Object.keys(todos).reduce( - (collection, id) => (!todos[id].completed ? { ...collection, id: todos[id] } : collection), - {} - ); + Object.keys(todos).forEach(id => { + if (!todos[id].completed) { + filteredTodos[id] = todos[id]; + } + }); + break; + + default: + filteredTodos = todos; break; } diff --git a/playground/src/reducers/createReducer.ts b/playground/src/reducers/createReducer.ts index 2225be6..4bae7fe 100644 --- a/playground/src/reducers/createReducer.ts +++ b/playground/src/reducers/createReducer.ts @@ -1,9 +1,9 @@ import { Reducer } from 'redux'; -import { ActionTypes, TodoAction } from '../actions'; +import { ActionTypes, TodoAction, TodoActionLookup } from '../actions'; import { Draft, produce } from 'immer'; -export type ImmerReducer = (state: Draft, action: TodoAction) => T; -export type HandlerMap = { [actionType in ActionTypes]?: ImmerReducer }; +export type ImmerReducer = (state: Draft, action: A) => T; +export type HandlerMap = { [actionType in ActionTypes]?: ImmerReducer }; function isHandlerFunction(handlerOrMap: HandlerMap | ImmerReducer): handlerOrMap is ImmerReducer { if (typeof handlerOrMap === 'function') { @@ -13,12 +13,16 @@ function isHandlerFunction(handlerOrMap: HandlerMap | ImmerReducer): ha return false; } -export function createReducer(initialState: T, handlerOrMap: HandlerMap | ImmerReducer): Reducer { - return function reducer(state = initialState, action: TodoAction): T { +export function createReducer( + initialState: T, + handlerOrMap: HandlerMap | ImmerReducer +): Reducer { + return function reducer(state = initialState, action: TodoAction | TodoActionLookup[AType]): T { if (isHandlerFunction(handlerOrMap)) { - return produce(state, draft => handlerOrMap(draft, action)); + return produce(state, draft => handlerOrMap(draft, action as TodoActionLookup[AType])); } else if (handlerOrMap.hasOwnProperty(action.type)) { - return produce(state, draft => handlerOrMap[action.type](draft, action)); + const handler = handlerOrMap[action.type] as ImmerReducer; + return produce(state, draft => handler(draft, action)); } else { return state; } diff --git a/playground/src/reducers/index.ts b/playground/src/reducers/index.ts index 13b4fff..880cbaf 100644 --- a/playground/src/reducers/index.ts +++ b/playground/src/reducers/index.ts @@ -2,6 +2,7 @@ import { createReducer } from './createReducer'; import { Store, FilterTypes } from '../store'; import { combineReducers } from 'redux'; import produce from 'immer'; +import { edit } from '../actions'; let counter = 0; @@ -32,10 +33,15 @@ export const reducer = combineReducers({ } }); return draft; + }, + + edit(draft, action) { + draft[action.id].label = action.label; + return draft; } } ), - filter: createReducer('all', (draft, action) => { - return action.filter; + filter: createReducer('all', (draft, action) => { + return action.filter as FilterTypes; }) });