I have a function createModule
which just returns its parameter:
function createModule(obj) {
return obj
}
The return value must have exactly the type inferred from the parameter:
interface Mod1State {
p1: string
}
const mod1 = createModule({
namespaced: true,
state: {
p1: "abc"
} as Mod1State,
mutations: {
SET_P1(state, p1: string) {
state.p1 = p1
}
}
} as const)
// 'mod1' must be of type: '{ namespaced: true, state: Mod1State, mutations: { SET_P1(state: any, p1: string): void } }'
So far, it's easy:
function createModule<T>(obj: T): T {
return obj
}
Now, I would like to add some autocompletion in the parameter state
of SET_P1
. And I'd rather check the state
property instead of casting it.
SET_P1(state, p1: string) {
// Here, 'state' should be of type Mod1State
}
Here is what I tried:
function createModule<S, T extends WithState<S> = WithState<S>>(obj: VuexModule<T, S>): T {
return obj
}
interface WithState<S> {
state?: S
}
type VuexModule<T extends WithState<S>, S = T["state"]> = T & {
namespaced?: boolean
state?: S
mutations?: {
[K: string]: (state: S, payload: any) => void
}
}
It works only if I remove the as const
(I don't understand why):
const mod1 = createModule<Mod1State>({
namespaced: true,
state: { // Good: the type of 'state' is checked
p1: "abc"
},
mutations: {
SET_P1(state, p1: string) { // Good: 'state' is of type 'Mod1State'
state.p1 = p1
}
}
})
But mod1
is now of type WithState<Mod1State>
. The inferred type is lost. How to restore the exact type of the createModule's parameter for the type of the return value?
See also: the example in the playground.
EDIT: I obtained something with a code derived from my example. I don't even understand how it works. And why the type of namespaced
is inferred as true
instead of boolean
without as const
.
I paste here a solution that seems to work. I'm not totally sure why and how.
interface Mod1State {
p1: string
}
function createModule<
T extends WithState,
S = T["state"]
>(obj: T & VuexModule<S>): T {
return obj
}
interface WithState {
state?: any | (() => any)
}
interface VuexModule<S> {
namespaced?: boolean
state?: S | (() => S)
mutations?: {
[K: string]: (state: S, payload: any) => void
}
}
const mod1 = createModule({
namespaced: true,
state: {
p1: "abc"
} as Mod1State,
mutations: {
SET_P1(state, p1: string) { // Good: 'state' is of type 'Mod1State'
state.p1 = p1
}
}
})
// Good: 'mod1' has the correct type,
// including 'namespaced: true' instead of 'namespaced: boolean'