Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Manually install vuex/types/vue.d.ts #994

Closed
blake-newman opened this issue Oct 16, 2017 · 19 comments
Closed

Feature: Manually install vuex/types/vue.d.ts #994

blake-newman opened this issue Oct 16, 2017 · 19 comments
Labels
4.x breaking change types Related to typings only

Comments

@blake-newman
Copy link
Member

blake-newman commented Oct 16, 2017

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 to vuex/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.

import Vue from 'vue'
import * as Vuex from 'vuex'
import { RootState } from 'store/types'

Vue.use(Vuex);

export class Store {
  store: Vuex.Store<RootState>;

  static instance: Store;

  constructor() {
    this.store = new Vuex.Store<RootState>{
      strict: true
    });
  }

  get() {
    return this.store
  }
}

declare module "vue/types/options" {
  interface ComponentOptions<V extends Vue> {
    store?: Vuex.Store<RootState>;
  }
}

declare module "vue/types/vue" {
  interface Vue {
    $store: Vuex.Store<RootState>;
  }
}

export default Store
@ktsn
Copy link
Member

ktsn commented Oct 18, 2017

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.

@mitchell-garcia
Copy link

mitchell-garcia commented Oct 19, 2017

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.

@yyx990803
Copy link
Member

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?

@blake-newman
Copy link
Member Author

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

@skovmand
Copy link

skovmand commented Oct 23, 2017

+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;
    }
  }
})

@blake-newman
Copy link
Member Author

I will move this to a discussion thread, as there is more we can do to make the Vuex API more type friendly.

@skovmand
Copy link

Sounds good. Will the discussion remain here, or can you give us a link to it?

@blake-newman
Copy link
Member Author

I will add a link, forming the thread atm including details on current issues.

@vuejs vuejs locked and limited conversation to collaborators Oct 24, 2017
@vuejs vuejs unlocked this conversation Jan 10, 2018
@blake-newman
Copy link
Member Author

@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

HerringtonDarkholme added a commit to HerringtonDarkholme/vuex that referenced this issue Mar 16, 2018
@ktsn ktsn added the types Related to typings only label Mar 31, 2018
@ysabri
Copy link

ysabri commented Apr 30, 2018

What is the state on adding this to the library?

@ZSkycat
Copy link

ZSkycat commented Jul 17, 2018

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

@therealmikz
Copy link

therealmikz commented Mar 2, 2019

Any plans on this?
I've struggled whole morning to try to override this declaration and found no way to achieve this.

@michalsnik
Copy link
Member

@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.

@therealmikz
Copy link

therealmikz commented Mar 6, 2019

@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.

@Niedzwiedzw
Copy link

Niedzwiedzw commented Dec 13, 2019

what's the recommended solution to allow for Store<MyState> instead of Store<any> now?

@budziam
Copy link

budziam commented Mar 27, 2020

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.

@kiaking
Copy link
Member

kiaking commented Apr 25, 2020

This issue has been solved at the 4.0.0-beta.1 🎉
https://github.com/vuejs/vuex/releases/tag/v4.0.0-beta.1

@andrewvasilchuk
Copy link

Checkout this article Vuex + TypeScript.

@cefn
Copy link

cefn commented Jan 29, 2022

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("");
    },
  },
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.x breaking change types Related to typings only
Projects
None yet
Development

Successfully merging a pull request may close this issue.