vuejs / vuex

🗃️ Centralized State Management for Vue.js.
https://vuex.vuejs.org
MIT License
28.42k stars 9.58k forks source link

Write Stores in Typescript #564

Open Anonyfox opened 7 years ago

Anonyfox commented 7 years ago

I couldn't find anything on google, so is this conveniently possible? Basically the bummer for me is that I have to call dispatch/commit with a given identifier string to trigger something.

My vision is to define stores as Typescript classes with typed methods (getting intellisense in editor and so on) which I can call instead of dispatch("my_action", data) and having to look up each and everything and check for errors manually all the time.

Basically my problem is that my team is building a fairly large Vue/Vuex frontend for a core product of our company, consisting of already 18 fairly complex Stores as modules, and this year stuff is going to at least quadruple in size and complexity. We already decided to go on with typescript instead of ES7 and are in the process of migrating our backend/frontend code, but this thing really feels like a bummer.

I think this is not the typical "small" use case for Vue.js, since we're building a huge enterprise frontend with loads of requirements and edge cases, but isn't vuex(/flux) supposed to scale up when complexity rises?

Has anyone experience in building complex, type safe Vuex 2.0 stores in Typescript yet? Any help here would be appreciated

pumpkinlink commented 5 years ago

Any official news on how this will probably be implemented on Vuex 4?

Seems that vuex-module-decorators is the most well-maintained workaround for now, but I'd like to at least know if the official solution will be somewhat similar to that one / vuex-simple (class-based store definition), or vuex-context / vuex-type-helper (object-based store with generated typed helpers).

That way me and my team can more easily migrate to Vue 3 / Vuex 4 in the future.

Thank you in advance

mrkswrnr commented 5 years ago

I also started working on yet another library for writing ES6/typescript classes as vuex modules without having to use decorators but with full type completion support. Also it is possible to write mutators (which I hope will be removed in vuex 4) as ES6 setter functions: vuex-typesafe-class.

I hope vuex 4 will be close to that (except the need to use mutators).

garyo commented 5 years ago

I'm also starting on a fairly large Vue/Vuex typescript app. I'm trying to figure out best practices for dir layout and module structure. I think this makes sense:

store/
  store.ts
  store-types.ts
  moduleA.ts
  moduleA-types.ts

but if I'm going to have submodules, maybe deeper nesting is best:

store/
  store.ts
  store-types.ts
  moduleA/
    moduleA.ts
    moduleA-types.ts
    subA/
      subA.ts
      subA-types.ts
  moduleB/
    moduleB.ts
    moduleB-types.ts

(I don't like lots of files named 'index.ts'; they all look the same. I prefer files to be named for what they are.) So does this latter structure look good? It seems like you do need a foo-types.ts for each module so the Vue components can import the module's shape, right? (I suppose you could put all types in store/store-types.ts though -- they don't create any Javascript output, right?) It does seem overly deeply nested to me; I don't like adding extra dir layers unless needed. Is there a "best practices" document on how to do this?

ClickerMonkey commented 5 years ago

I tackled it here - with no additional code, just better typing:

https://github.com/ClickerMonkey/vuex-typescript-interface

I started where @danielfigueiredo did, but figured out I could automatically detect getters, mutations, and actions all from one interface.

So now state, getters, commit, and dispatch all require valid strings and types or TS will throw an error. If you also have state/getters/actions/mutations defined on your interface and NOT in your store it will throw an error. The getter/mutation/action definitions passed in as options must have the proper types or it will also throw an error here as well!

jdriesen commented 5 years ago

Hi All,

Super interesting comments. Thank you all !

If I can ask ... (we're now October 2019 and we're all using Vue 3) ... Any official news on this subject ? In other words ... What's the proper way to implement Vuex Typescript in a Vue 3 Project ?

Regards, Johnny

garyo commented 5 years ago

I don't know about Vue 3 (I'm waiting for the official release) but I do have a reasonably clean, complete Typescript-typesafe vuex-module-decorators example at https://github.com/garyo/vuex-module-decorators-example.git if you're interested.

paleo commented 5 years ago

I found a solution for the code that uses the store and calls dispatch, commit, getters and the state. For example:

store.dispatch("myModule/myAction", myPayload);

… is replaced by a wrapper:

store.dispatch.myModule.myAction(myPayload);

… which is fully typed.

https://github.com/paleo/direct-vuex

Kriget commented 4 years ago

this worked for me:

// in vuexCommitFuncVars.ts
export default {
  decreaseCount: "decreaseCount",
  ...
};

// in actions.ts
import funcVars from "./vuexCommitFuncVars"; 
commit(funcVars.decreaseCount);
// before i had to write 'commit("decreaseCount")'

// in mutations.ts
[funcVars.decreaseCount](state) {
  state.count= state.count- 1;
},
AlenQi commented 4 years ago

Any official news in 2020?

TotomInc commented 4 years ago

IIRC, vue next (aka vue 3) is on an alpha release, we still need to wait for the official vue ecosystem to update, and in the future this will (most probably) entirely handled natively by vuex.

Please correct me if I’m wrong.

MikeMitterer commented 4 years ago

@AlenQi I'm using Vue 2 and Vuex with TS without any problems. Here you can see how I use it: https://github.com/MikeMitterer/vuetify-ts-starter/blob/master/src/views/About.vue (CounterStore) And here https://github.com/MikeMitterer/vuetify-ts-starter/tree/master/src/store how everything is glued together. Hope this helps.

viT-1 commented 4 years ago

@AlenQi Vue 2 typescript examples with vuex & without webpack/rollup (tsc transpiling & bundling):

F0rsaken commented 4 years ago

So I made a small library wich allows you to get fully typed store in actions and components. It also adds helpers that allow you to import rather than us mapXXX or decorators

https://github.com/owlsdepartment/vuex-typed

gcollombet commented 4 years ago

For me adding the store state type in module declaration is enough to have auto-completion

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

A more complete exemple :


import Vue from "vue"
import Vuex, { StoreOptions} from "vuex"
import App from './App.vue'

Vue.use(Vuex)

interface RootState {
  user: App.Models.User | null;
  notifications: App.Models.Notification[];
}

const store: StoreOptions<RootState> = {
  state: {
    user: null,
    notifications: []
  },
  mutations: {
    login(state, user: User) {
      state.user = user
    },
    logout(state) {
      state.user = null
    }
  },
  actions: {
  },
  modules: {
  }
}

store = new Vuex.Store<RootState>(store);

declare module "vue/types/vue" {
  interface Vue {
    // https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#use-union-types
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    $store: Store<RootState| any>;
  }
}

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
sethidden commented 4 years ago

The can't overwrite Store<any> issue (originally tracked in #994) will be resolved in Vuex 4 - beta version release notes here

rfox12 commented 4 years ago

Vuex has created a lot of pain for Typescript users. Now that we have the Composition API... do we even really need Vuex? In 2020, if I'm training a team to use Vue 3, why would I teach them Vuex? Creating stateful singletons that you can compose is a really nice pattern. Code is so much cleaner without Vuex.

The arguments in favor of Vuex (in my mind) are:

What am I missing?

yoyoys commented 4 years ago

Vuex has created a lot of pain for Typescript users. Now that we have the Composition API... do we even really need Vuex? In 2020, if I'm training a team to use Vue 3, why would I teach them Vuex? Creating stateful singletons that you can compose is a really nice pattern. Code is so much cleaner without Vuex.

The arguments in favor of Vuex (in my mind) are:

  • Vue devTools integration (time travel, etc.)
  • Slightly easier to avoid name collisions with namespace modules?

What am I missing?

Agree, I've trained my teammate to use Vue's injection to provide shared data between components.

garyo commented 4 years ago

This is true, but Vue dev tools integration is not a small thing. It can be extremely useful for debugging.

tonnyorg commented 4 years ago

I'm kinda facing the same issue here, this is how my actions.ts looks like:

export default {
  updateIp(context: any, ip: string) {
    context.commit('setIp', ip);
  }
}

But I get this warning:

  2:21  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any

Any idea how to fix this?

rfox12 commented 4 years ago

A shout out to @paleo, direct-vuex is fantastic. Strongly typed coding within the store and (perhaps more importantly) when components access the store state and actions.

I tried Vuex 4 today, but it didn't really do much for me (I'm still on Vue2 + Composition add-on). Is there an outline of how Vuex 4 will work for Typescript users? All I could find was an old roadmap with generic statements about "supporting Typescript"

sethidden commented 4 years ago

@rfox12 As far as I know, you'll need to wait for Vuex 5. Vuex 4 was focused on preparing for Vue 3 and allowing passing your own interface to Store<>

Before Vuex 4, you'd have to do:

new Vue({
  store: new Store(store) as Store<any>
})

thus this.$store.state wouldn't be typed. Working around that (passing your own store interface instead of any) required some trickery

rfox12 commented 4 years ago

I'll add two additional compelling reasons to keep Vuex in Composition API world:

So in short... I'm keeping Vuex for large projects, but I have an question. First some background. To get typescript support today I find it best to import store from './store'; and then use something like store.dispatch.core.signOut() (with direct-vuex). Inside my single-page components. This is instead of using this.$store (which I cannot get Typescript to understand yet--still on Vuex 3). My question is: is there anything magical about Vue's instance of the store that I should be aware of? Any wrapping or logic that would make this.$store different from the imported version?

kiaking commented 4 years ago

@rfox12 You should always use this.$store if you're doing SSR. Otherwise, it doesn't matter.

The difference with direct import is that when you use this.$store, you're using the store instance injected into the Vue instance. When doing SSR, at server side, if you use direct import, the store instance will be shared between the different requests because the store instance becomes a global singleton. You might get state pollution.

If you're not doing SSR, it's fine to directly import the store since it will be used by only one client (in browser).

MikeMitterer commented 4 years ago

@Teebo Here is my solution: https://github.com/MikeMitterer/vue-ts-starter/tree/master/src/store

Teebo commented 4 years ago

@MikeMitterer thank you so much, I will go through the repo to understand the setup, but from a glance, it looks good, thank you!

Teebo commented 4 years ago

@MikeMitterer, in the file https://github.com/MikeMitterer/vue-ts-starter/blob/master/src/store/utils/index.ts The isNotRegistered func: Is the check mainly for checking if the child store module is registered or it also checks the existence of the RootStore and if it has a state? I am just wondering if the built-in hasModule could be used.

I see that in the actions return promises in the counter store, is the a requirement fromvuex-module-decorators or it is just a usecase example?

RandomErrorMessage commented 4 years ago

Been subscribed to this issue for like 2 years, the absolute state of webdev. Glad I dropped this stuff, yikes.

ChanningHan commented 3 years ago

It is v4.0.0-rc.2 now. But it seems to haven't no TypeScript supporting for Mutations and Actions. Why do the Mutations or Actions have to be used by giving identifier string to trigger?
So, will there be any new feature or API to implement type suporting for mutations and actions?

ClickerMonkey commented 3 years ago

@ChanningHan I've been working on full type support for vuex in my personal time, and with TS 4.1 it seems definitely possible. The only thing holding it back is a problem with TS where it obnoxiously warns about "potentially infinite blah blah". I will be updating this issue: https://github.com/vuejs/vuex/issues/1831

ChanningHan commented 3 years ago

@ClickerMonkey Bravo~It will be exciting! ActuaIly, I used to be a heavy user of Vuex before using TS. So, I really hope to see the full type support for vuex, and thank you for your hard working on it♥

tarkhil commented 3 years ago

Spent several hours trying to modularize vuex store with TypeScript, I feel TS is just a way to write your code two times more. I do understand the importance of type safety, but I gave up trying to describe all types...

charles-allen commented 3 years ago

Spent several hours trying to modularize vuex store with TypeScript, I feel TS is just a way to write your code two times more. I do understand the importance of type safety, but I gave up trying to describe all types...

TypeScript works best when you just describe a few core domain types & the rest of your code has types inferred from how you operated on those core types & library types. It's especially important for libraries/frameworks to export APIs with types; a few anys sneaking in from libraries creates a lots of manual typing overhead.

francoism90 commented 3 years ago

How should you write getters? I'm using this example: https://next.vuex.vuejs.org/guide/typescript-support.html#simplifying-usestore-usage

Doing store.getters['foo/bar'] doesn't work because it returns 'any', how do you type hint these?

sethidden commented 3 years ago

You can't. Its the same as in Vue 2. The newest version of Vuex is mainly for Vue 3 support. Wait for Vuex 5 or use vuex-module-decorators (or similar pkgs).

There's also an issue in this repo that proposes using TS 4.1 template literal types for typing

@francoism90

francoism90 commented 3 years ago

@3nuc I'm new to TypeScript so don't know what any of that means lol. As a workaround I'll be sticking to mapGetters as TS doesn't complain about this. State injection seems to working, but using helpers defeats the point of TS I think.

Will take a look at vuex-module-decorators, although I'll probably wait for Vuex 5. Thanks!

usernamehw commented 3 years ago

https://github.com/posva/pinia - looks pretty good. Works with TypeScript out of the box.

francoism90 commented 3 years ago

@usernamehw Yeah, I also using Pinia as TS replacement. :)

championswimmer commented 3 years ago

People who prefer classical OOP-based concepts and not too functional looking code, can check out my library

https://github.com/championswimmer/vuex-module-decorators