Let's explore a JavaScript function and its usage:
const fooHandler = function(state, foo) {
if (foo) {
return {...state, foo}
} else {
return state.foo
}
}
const currentState = {foo: 'foo', other: 'data'};
fooHandler(currentState); // returns 'foo'
fooHandler(currentState, 'bar'); // return {foo: 'bar', other: 'data'}
// This function can be used to interact with state dynamically
function handleInteraction(state, handlerFn) {
// e.g. handlerFn(state);
}
handleInteraction(currentState, fooHandler);
How would I implement this in TypeScript?
I need to define a type that satisfies Typescript requirements:
type StateHandler {
(state: State): string
(state: State, value: string): State
}
function performInteraction(state: State, stateFn: StateHandler) {
const data: string = stateFn(state);
const updatedState: State = stateFn(state, data);
// etc
}
However, there are challenges in implementing StateHandler
:
const fooHandler: StateHandler = function(state: State, foo?: string) {
if (foo) {
return {...state, foo} as State
} else {
return state.foo as string
}
}
/*
Type '(state: State, foo?: string) => string | State' is not assignable to type 'StateHandler'.
Type 'string | State' is not assignable to type 'string'.
Type 'State' is not assignable to type 'string'.ts(2322)
*/
Arrow functions/lambda expressions also present difficulties.
One workaround is using function overloading:
function fooHandler(state: State): string
function fooHandler(state: State, value: string): State
function fooHandler(state: State, value?: string) {
if (value) {
return {...state, foo: value} as State
} else {
return state.foo as string
}
}
// This approach works, but it feels clunky and repetitive
const typedFooHandler: StateHandler = fooHandler
performInteraction(someState, typedFooHandler)
This method is somewhat awkward and necessitates redefining the StateHandler type with every implementation.