Closed blake-newman closed 4 years ago
I agree that we should manually declare $store
on Vue instance type.
But I also think there are no need to do that for ComponentOptions
. It might be better to separate instance type augmentation and component options type augmentation.
This would be a huge boost in productivity for us Typescript users - current examples are extremely verbose, especially when it comes to writing getters. In order to get static typing on any namespaced property from the $store, you have to write a getter and a storeAccessor (provided by vuex-typescript
) for it.
import { getStoreAccessors } from 'vuex-typescript';
export const getters = {
getProperty(state: ExampleState) {
return state.property;
}
}
const { read } = getStoreAccessors<ExampleState, ExampleRootState>('example');
export const getProperty= read(getters.getProperty);
Being able to specify the structure for this.$store on a per-project basis would save so many hours of work, I would like to pick this up if it's agreed that it would be the right direction.
I think this is a good idea, my only concern is backwards compatibility since we just released a new major version. Is there anyway to tell TS not to use the included types?
As far as I can see no, looks like it'll be a complete breaking change. If doing that we may aswell plan to improve typing layer where we can.
@ktsn has done some awesome work over here. https://github.com/ktsn/vuex-type-helper
+1 for this.
I came here to start the same discussion.
We currently solve this by manually type casting this.$store
to our own store interface to get typed variables from the store. It gets pretty verbose writing (<OurStore>this.$store)
. If we could define type of this.$store
globally, it would solve this.
interface OurState {
search: {
query: string | null;
results: ImageSearchResult[];
viewType: boolean;
}
}
interface OurStore extends Store<OurState> {}
export default Vue.extend({
name: "search-result",
template: searchResultTemplate,
computed: {
results(): ImageSearchResult[] {
return (<OurStore>this.$store).state.search.results;
},
viewType() {
return (<OurStore>this.$store).state.search.viewType;
}
}
})
I will move this to a discussion thread, as there is more we can do to make the Vuex API more type friendly.
Sounds good. Will the discussion remain here, or can you give us a link to it?
I will add a link, forming the thread atm including details on current issues.
@ktsn Has done some great work in getting Vuex more type safe. Thus this issue can be solved with his work, as we can do a major semver upgrade
What is the state on adding this to the library?
tsconfig.json
{
"file": [
"./node_modules/vue-router/types/vue.d.ts",
"./node_modules/vuex/types/vue.d.ts",
],
"include": [
"./src/**/*",
],
}
I have always import the description file manually. Is your automatic import?
I actually prefer to use import. I may have multiple entry files, multiple vuex instances.
import { store } from 'store.ts'
Change the type as you like
export default store as YourType
Any plans on this? I've struggled whole morning to try to override this declaration and found no way to achieve this.
@therealmikz I think that the only way to patch this at this moment is to use https://www.npmjs.com/package/patch-package
It's not the best solution I know, but at least it works and makes your store state typed until it's officially solved.
@michalsnik thanks, I've seen this one before and I was planning to check it out
Edit: it worked. It's definitely not an elegant solution, but it does the job
Btw. I recommend not to change Store<any>
to Store<MyState>
inside of vue.d.ts
, but to remove this entirely and put typings into project code.
what's the recommended solution to allow for Store<MyState>
instead of Store<any>
now?
I would suggest going with a new, custom field that returns $store
instance, but can be strongly typed.
Object.defineProperty(Vue.prototype, "$stock", {
get(): Store<RootState> {
return this.$store;
}
});
declare module "vue/types/vue" {
interface Vue {
$stock: Store<RootState>;
}
}
Now, all you need to do is to replace usage of this.$store
with this.$stock
everywhere.
This issue has been solved at the 4.0.0-beta.1
🎉
https://github.com/vuejs/vuex/releases/tag/v4.0.0-beta.1
Checkout this article Vuex + TypeScript.
If a workaround is still valuable (for those locked to a Vue2 codebase, for example), I found the following to be useful.
It wraps the Vuex' builtin mapState
, mapping from this.$store
to named properties on this
but inferring the correct type of the computed methods from the State
type you pass.
import { mapState } from "vuex";
export function mapTypedState<State>(keys: (string & keyof State)[]) {
type Key = typeof keys[number];
return mapState(keys) as {
[key in Key]: () => State[key];
};
}
In your store definition you can create a type-specific call like this if you want...
export function mapRootState(keys: (keyof RootState)[]) {
return mapTypedState<RootState>(keys);
}
This can very tersely, and type-safely, construct locally mapped properties from the store's state. Note Vuex mapState calls can be namespaced if you want to pick out typed state from within a module.
The code example below constrains the type of this.message
to string, and Volar tooling can autocomplete the name 'message' and the method .split()
within my HelloWorld.vue
component (see the PR against a repro of the Vue2 problem case)...
import { mapRootState } from "@/store";
import Vue from "vue";
export default Vue.extend({
name: "HelloWorld",
computed: {
...mapRootState(["message"]),
backwardsMessage(): string {
const chars = this.message.split("");
chars.reverse();
return chars.join("");
},
},
});
This is a breaking change
What problem does this feature solve?
With module merging in TS, we can override module interfaces. However it is limited, in the case of the Vuex types it's impossible to compose full typing layer in components.
If we make it optional to install
vuex/types/vue.d.ts
then we can do our own manual declaration. Which will enable fully typed structures. I would also rename tovuex/types/install.d.ts
What does the proposed API look like?
Once we allow custom installation we can do something similar to this to enable full typing layer.