My aim is to create a function which has two parameters. First one is union value of type
field in Action
and second parameter should force developer to use specific object in Action
.
For example, assume that we have this function function dispatch(actionType, payload){}
dispatch('SIGN_OUT', { type: 'SIGN_OUT', userName: ''
});
or
dispatch('SIGN_IN_FAILURE', { type: 'SIGN_IN_FAILURE', error: '' });
type Action =
| { type: 'INIT' }
| { type: 'SYNC' }
| { type: 'SIGN_UP', userName: string, password: string, attributeList: Array<any> }
| { type: 'SIGN_IN', userName: string, pasword: string }
| { type: 'SIGN_IN_SUCCESS', accessToken: string }
| { type: 'SIGN_IN_FAILURE', error: string }
| { type: 'SIGN_OUT', userName: string }
| { type: 'FORGOT_PASSWORD', userName: string }
| { type: 'FORGOT_PASSWORD_SUCCESS', verificationCode: string }
| { type: 'CHANGE_PASSWORD', userName: string, oldPassword: string, newPassword: string }
| { type: 'CONFIRM_REGISTRATION', userName: string, confirmationCode: string }
| { type: 'RESEND_CONFIRMATION_CODE', userName: string }
| { type: 'DELETE_USER', userName: string }
| { type: 'GET_SESSION', userName: string }
| { type: 'GET_SESSION_SUCCESS', session: string };
type TodoAction =
| { type: 'INIT' }
| { type: 'ADD_TODO', text: string }
| { type: 'REMOVE_TODO', id: string }
| { type: 'SET_COMPLETED', id: string };
Wrong usage
Notice that payload
parameter in line 32 is Action
type, so I can create any object in Action
type.
type DispatchType = (actionType: Action['type'], payload: Extract<Action, { type: Action['type'] }>) => void;
const dispatch: DispatchType = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('CHANGE_PASSWORD', { type: 'DELETE_USER', userName: '' });
Still Wrong usage
Notice that when I write comma character in line 39, intellisense popup show me that I can use { type: 'CHANGE_PASSWORD', ....}
, this is expected thing.
But I could use { type: 'DELETE_USER', ....}
, so it is not expected behaviour, VSCode should error normally.
type DispatchType = <K extends Action['type']>(actionType: K, payload: Extract<Action, { type: K }>) => void;
const dispatch: DispatchType = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('CHANGE_PASSWORD', { type: 'DELETE_USER', userName: '' });
Good usage
type DispatchType = <K extends Action['type'], J extends Extract<Action, { type: K }>>(actionType: K, payload: J) => void;
const dispatch: DispatchType = (actionType, payload) => { };
dispatch('SIGN_IN', { type: 'SIGN_IN', userName: '', pasword: '' });
dispatch('CONFIRM_REGISTRATION', { type: 'CONFIRM_REGISTRATION', userName: '', confirmationCode: '' });
Good usage
Notice that I just add a new generic type T extends Action
and it is used in K extends T['type']
so instead of using K extends Action['type']
, K extends T['type']
is used.
type DispatchType = <T extends Action, K extends T['type']>(actionType: K, payload: Extract<T, { type: K }>) => void;
const dispatch: DispatchType = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('DELETE_USER', { type: 'DELETE_USER', userName: '' });
dispatch('CONFIRM_REGISTRATION', { type: 'CONFIRM_REGISTRATION', userName: '', confirmationCode: '' });
Better usage
type DispatchType = <T extends Action, K extends T['type'], J extends Extract<T, { type: K }>>(actionType: K, payload: J) => void;
const dispatch: DispatchType = (actionType, payload) => { };
dispatch('SIGN_IN', { type: 'SIGN_IN', userName: '', pasword: '' });
dispatch('GET_SESSION_SUCCESS', { type: 'GET_SESSION_SUCCESS', session: '' });
Ideal usage
Notice that I moved T extends { type: any }>
in DispactType<T extends { type: any }>
so that I could use dispatch for Action
type and TodoAction
type.
type DispatchType<T extends { type: any }> = <K extends T['type'], J extends Extract<T, { type: K }>>(actionType: K, payload: J) => void;
const dispatch: DispatchType<Action> = (actionType, payload) => { };
dispatch('SIGN_IN', { type: 'SIGN_IN', userName: '', pasword: '' });
const dispatch_: DispatchType<TodoAction> = (actionType, payload) => { };
dispatch_('ADD_TODO', { type: 'ADD_TODO', text: '' });
dispatch_('SET_COMPLETED', { type: 'SET_COMPLETED', id: '' });
Almost Best usage
Notice that payload doesn't require type
field anymore.
type DispatchType<T extends { type: any }> = <K extends T['type'], J extends Omit<Extract<T, { type: K }>, 'type'>>(actionType: K, payload: J) => void;
const dispatch: DispatchType<Action> = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('INIT', {});
dispatch('SIGN_IN', { userName: '', pasword: '' });
dispatch('CHANGE_PASSWORD', { userName: '', oldPassword: '', newPassword: '' });
Maybe better than best usage
type DispatchType<T extends { type: any }> = <K extends T['type'], J extends Omit<Extract<T, { type: K }>, 'type'>>(actionType: K, payload: J) => void;
const dispatch: DispatchType<Action> = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('INIT', {});
dispatch('SIGN_IN', { userName: '', pasword: '' });
dispatch('CHANGE_PASSWORD', { userName: '', oldPassword: '', newPassword: '' });