vuex
vuex copied to clipboard
feat(types): support experimental stricter types
This is a fully compatible type-only feature which is able to greatly improve the development experience!
It added a type-only option stricterTypes
:
const store = createStore(
{
stricterTypes: true, // <--- set the type-only option `stricterTypes` to `true`
// ...
},
);
Then, the store
object's type will be StricterStore<...>
, which provides a lot of exciting type features:
-
store.state
now contains the state of all the modulesstore.state.module1.module2.deepState;
-
store.getters
is now strictly typed, it not only contains the types of the root getters but also the ones in namespaced (namespaced: true
) modulesstore.getters["module1/module2/deepGetter"]; // auto-completion supported
-
store.commit()
andstore.dispatch()
are super intelligent now, the first paremetertype
supports auto-completion of the paths to both the root mutations/actions and the ones in namespaced modules, and the type of the second parameterpayload
will be the same as the type of the second parameter of the target mutation/action. Whatsmore, the return type ofstore.dispath
will be the same as the return type of the target action. Ofcourse it supports both(type, payload, options)
and(payloadWithType, options)
const store = createStore(
{
stricterTypes: true,
state: { state1: 1 },
getters: { getter1: () => 1 },
mutations: { mutation1: (state, payload: { a: string }) => {} },
actions: { action1: async (context, payload: { a: string }) => 1 },
modules: {
module1: {
namespaced: true,
state: { state2: "" },
getters: { getter2: () => "" },
mutations: { mutation2: (state, payload: { b: number }) => {} },
actions: { action2: async (context, payload: { b: number }) => "" },
modules: {
module2: {
namespaced: false,
state: { state3: true },
getters: { getter3: () => true },
mutations: { mutation3: (state, payload: { c: boolean }) => {} },
actions: {
action3: async (context, payload: { c: boolean }) => true,
},
},
},
},
},
}
);
Known Problem: The stricter types have great benefits in using the store
, but will cause the state
to be any
when defining the store.
好耶!
Drive-by comment: thanks for making this! Scary to see that TypeScript metaprogramming goes this deep (path generation).
Hoping to help test this.
- Would it be possible to make
stricterTypes
part of theoptions
argument instead? It looks strange to add a new dummy argument. - Inferring state, this should also remove the need for typing
State
separately, right? That suggests https://github.com/vuejs/vuex/blob/4.0/docs/guide/typescript-support.md needs an update.
Drive-by comment: thanks for making this! Scary to see that TypeScript metaprogramming goes this deep (path generation).
Hoping to help test this.
- Would it be possible to make
stricterTypes
part of theoptions
argument instead? It looks strange to add a new dummy argument.- Inferring state, this should also remove the need for typing
State
separately, right? That suggests https://github.com/vuejs/vuex/blob/4.0/docs/guide/typescript-support.md needs an update.
Tests are coming soon... :]
- Yes, that's easy, I will implement it right now. :-)
- I'm not very sure about the behavior of the stricter types in actually use, there may probably be much unknown problems, so I think it may be better to wait for the stricter types to get mature enough?
I think it may be better to wait for the stricter types to get mature enough?
I agree with the sentiment, but I'm guessing the PR is more likely to be accepted if it includes a documentation update. Since this is an opt-in extension, I'd assume it would be about adding a new section rather than replacing the existing. But I'm not a maintainer, so I understand if you'd want to wait for a review.
LGTM, it's a long term problem.
Some thoughts after converting a PoC:
-
Using
type Store = typeof store
andInjectionKey<Store>
makes more sense now. Sadly that doesn't have a nice fallback for when stricter types are disabled (since the oldState
interface isn't used). TheState
then becomesunknown
and type checking in mutations fails. I wonder if there's a middle-ground that could unify them during a transition. -
Needs
export function useStore<S extends StricterStore<any>>(injectKey: InjectionKey<S>, stricterTypes: true): S;
-
It would be nice to be able to avoid
stricterTypes
inuseStore
, of course. -
I use a pattern of giving the store reference and a mutation name as a string to some constructors. This fails because the union type of mutation string literals can't take a string. The type extraction could probably be made more readable:
private store: Store, private name: Parameters<typeof store.commit>[0]
Some thoughts after converting a PoC:
- Using
type Store = typeof store
andInjectionKey<Store>
makes more sense now. Sadly that doesn't have a nice fallback for when stricter types are disabled (since the oldState
interface isn't used). TheState
then becomesunknown
and type checking in mutations fails. I wonder if there's a middle-ground that could unify them during a transition.- Needs
export function useStore<S extends StricterStore<any>>(injectKey: InjectionKey<S>, stricterTypes: true): S;
- It would be nice to be able to avoid
stricterTypes
inuseStore
, of course.- I use a pattern of giving the store reference and a mutation name as a string to some constructors. This fails because the union type of mutation string literals can't take a string. The type extraction could probably be made more readable:
private store: Store, private name: Parameters<typeof store.commit>[0]
Thanks~❤
-
createStore()
now accepts the virtual parameterstricterTypes
in its options:createStore({ stricterTypes: true; // ... })
-
useStore()
now supports bothStore
andStricterStore
without any extra parameters:const key: InjectionKey<typeof store> = Symbol(); const store_ = useStore(key); // type: StricterStore<...>
- Sorry, I didn't understand your case well... :[
If you mean to have the literal string type of mutations, the typeCommitType
can be used, but to extract the literal string type of the mutations directly from astore
, you may have to create a tool type:type ExtractCommitType< Store extends StricterStore<any, any, any, any, any> > = Store extends StricterStore< infer RootState, any, infer Mutations, any, infer Modules > ? CommitType<RootState, Mutations, Modules> : never;
Thanks for the fixes!
createStore() now accepts the virtual parameter stricterTypes in its options:
I needed to move the stricterTypes
overload first (just like with useStore
) for type inference to work. Without it, the state
argument to mutator functions became unknown
.
type ExtractCommitType
Yepp, I think you understood, and that's the problem I wanted to solve:
function doit(store: Store, name: ExtractCommitType<Store>) {
store.commit(name);
}
Assuming other people have been doing the same thing, I think this would be useful to add as part of the API. (Same with actions.)
Given that TypeScript has ReturnType<>
and Parameter<>
(as opposed to ExtractReturnType<>
), I don't think the name is great, but that's a tiny thing.
Thanks for the fixes!
createStore() now accepts the virtual parameter stricterTypes in its options:
I needed to move the
stricterTypes
overload first (just like withuseStore
) for type inference to work. Without it, thestate
argument to mutator functions becameunknown
.type ExtractCommitType
Yepp, I think you understood, and that's the problem I wanted to solve:
function doit(store: Store, name: ExtractCommitType<Store>) { store.commit(name); }
Assuming other people have been doing the same thing, I think this would be useful to add as part of the API. (Same with actions.)
Given that TypeScript has
ReturnType<>
andParameter<>
(as opposed toExtractReturnType<>
), I don't think the name is great, but that's a tiny thing.
Yes, that's a known problem, I haven't found a way to make the type inference work well in the options of createStore()
... T_T
I just realized that this kind of naming style does have some problems, there are also some built-in tool types in TypeScript are named like Extract
, Exclude
, Pick
and Omit
, maybe it would be better to use StoreCommitType
.
It's a good idea to add tool types like this to the API. Although tool types like this are quite easy to implement, a large number of TypeScript users may still not be able to implement them. More tool types will be available soon.🎉
By the way, is there a code format style specified in this repo? I didn't found a config like prettier.config.js
here, and formatting the code with prettier or directly using VSC's built-in formatter will both cause a lot of changes. I had to format only part of the code to avoid conflicts. :[