I'm embarrassed asking this because I've spent hours looking at other questions, and I know this has been asked a dozen times, yet I remain confused.
I am using typescript and Vuex, and am trying to make a wrapper around an object of Getters to check if that module has been initialized first.
The use would look something like
//index.ts
import { Module } from 'vuex';
import { makeSafeGetters } from './safeGetters'
export const testModule: Module<testState, RootState> = {
getters: makeSafeGetters(getters),
//safeGetters.ts
/**
* This takes the getters from a module and wraps in a safety call that ensures
* you are not accessing uninitialized stores
*
* @param getterList - the getter tree exported from the `<module>.getters.ts`
*/
export function makeSafeGetters<U extends BaseState<any>>
(getterList: GetterTree<U, RootState>) {
const getters: GetterTree<U, RootState> = {};
for (const key in getterList) {
if (Object.prototype.hasOwnProperty.call(getterList, key)) {
const getter = getterList[key];
getters[key] = safeGetter(getter);
}
}
return getters;
}
/**
* Wraps a getter with a call to make sure the store has been initialized
*
* @param getter - the getter to be wrapped
*/
function safeGetter<V, T extends Getter<V extends BaseState<any> ? V : never, RootState>>
(getter: T): T {
return (state, ...args): ReturnType<T> => {
if (!state.initialized && !state.loading) {
console.error(`${getter.name}: <ModuleNameHere> not initialized, make sure store has been fetched.\
Check stack for module name`,
{ state });
}
return getter(state, ...args);
};
}
//type.ts
export interface BaseState<T> {
initialized: boolean;
loading: Promise<any> | null | boolean;
values: T;
}
My problem is in the safeGetter
function. On the line return (state, ...args)
I get the following error
Type '(state: V extends BaseState<any> ? V : never, getters: any, rootState: RootState, rootGetters: any) => ReturnType<T>' is not assignable to type 'T'.
'(state: V extends BaseState<any> ? V : never, getters: any, rootState: RootState, rootGetters: any) => ReturnType<T>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Getter<V extends BaseState<any> ? V : never, RootState>'.ts(2322)
Something that is important to me is that the types of the getters are transparent through makeSafeGetters
, such that if I hover over makeSafeGetters
in index.ts
I get
(alias) makeSafeGetters<TestState>(getterList: GetterTree<TestState, RootState>): GetterTree<TestState, RootState>
A similar question was asked here https://stackoverflow.com/a/59148659/6100005, but I don't know how to apply that to my circumstance without clobbering the types
(getter: (state: S, ...args: A) => R): (state: S, ...args: A) => R` ? I think the problem arises from the unconstrained nature of `T`: if you don't know the specific type of a function typed as `T`, then you will have trouble convincing the type system you have exactly replicated its type. Your `T extends Getter<...>` is an upper bound, but the **actual** type of `T` imposes a stricter upper bound.– jtbandes Sep 08 '20 at 22:20