Let's say I have a generic function with the following definition:
function createSlice<
State,
CaseReducers extends SliceCaseReducers<State>,
Name extends string,
Selectors extends SliceSelectors<State>,
ReducerPath extends string = Name,
>(
options: CreateSliceOptions<State, CaseReducers, Name, ReducerPath, Selectors>
): Slice<State, CaseReducers, Name, ReducerPath, Selectors> {
return options as Slice<State, CaseReducers, Name, ReducerPath, Selectors>;
}
// Let's define some types for simplicity:
export type SliceCaseReducers<State> = State & { someField: State };
export type SliceSelectors<State> = State & { someOtherField: State };
export type CreateSliceOptions<State, CaseReducers, Name, ReducerPath, Selectors> = {
a: State;
b: CaseReducers;
c: Name;
d: ReducerPath;
e: Selectors;
};
export type Slice<State, CaseReducers, Name, ReducerPath, Selectors> = {
a: State;
b: CaseReducers;
c: Name;
d: ReducerPath;
e: Selectors;
};
And I want to create a createSliceWithSideEffects
function which would look like this:
const doSomeSideEffects = (slice: unknown) => {
console.log(slice);
};
const createSliceWithSideEffects = (options: any) => {
const slice = createSlice(options);
doSomeSideEffects(slice);
return slice;
};
But the problem is typing that function. The only solution I see is to copy-paste all of the createSlice
generic parameters and define the createSliceWithSideEffects
function like this:
const doSomeSideEffects = (slice: unknown) => {
console.log(slice);
};
const createSliceWithSideEffects = <
State,
CaseReducers extends SliceCaseReducers<State>,
Name extends string,
Selectors extends SliceSelectors<State>,
ReducerPath extends string = Name,
>(
options: CreateSliceOptions<State, CaseReducers, Name, ReducerPath, Selectors>
): Slice<State, CaseReducers, Name, ReducerPath, Selectors> => {
const slice = createSlice(options);
doSomeSideEffects(slice);
return slice;
};
Which is quite cumbersome and requires updating the definition each time the definition of createSlice
changes. Ahother problem might be that SliceCaseReducers
and SliceSelectors
are not exported by a library and you would have to copy these types' definitions as well.
Is there a way to do it without copying all of the parameters? So it could look something like this while also preserving the generic types:
const createSliceWithSideEffects = modifiedGenericFunction(createSlice, (...params) => {
const slice = createSlice(...params);
doSomeSideEffects(slice);
return slice;
});
You can play with it in a typescript playground
You can use TypeScript's support for higher order type inference from generic functions to transform generic functions in a way that preserves their type parameters. It looks like you want modifiedGenericFunction(originalFunc, modifiedFunc)
to return modifiedFunc
at runtime, but to use originalFunc
's call signature including all the generics. That can be done this way:
function modifiedGenericFunction<A extends any[], R>(
originalFunc: (...a: A) => R,
modifiedFunc: (...a: A) => R
): (...a: A) => R {
return modifiedFunc
}
By making modifiedGenericFunction
generic in the parameters type A
and the return type R
of the input functions, TypeScript will automatically "lift" the generic call signature of originalFunc
into A
and R
in the way you want:
const createSliceWithSideEffects = modifiedGenericFunction(createSlice, (...params) => {
const slice = createSlice(...params);
doSomeSideEffects(slice);
return slice;
});
/* const createSliceWithSideEffects: <
State, CaseReducers extends SliceCaseReducers<State>,
Name extends string, Selectors extends SliceSelectors<State>,
ReducerPath extends string = Name
>(options: CreateSliceOptions<State, CaseReducers, Name, ReducerPath, Selectors>) =>
Slice<State, CaseReducers, Name, ReducerPath, Selectors> */
Looks good. The type of createSliceWithSideEffects
is exactly the same as createSlice
, including the generic constraints and default type arguments.