vuejs / vuex

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

how to use modules in vuex4.0 #1833

Open devin-huang opened 4 years ago

devin-huang commented 4 years ago

Version

4.0.0-beta.4

import { createStore } from 'vuex'
import * as types from './mutation-type'

export default createStore({
  modules: {
    common: {
      namespaced: true,
      state: {
        device: '',
      },
      mutation: {
        [types.SET_DEVICE_LIST](state: any, device: string) {
          state.device = device
        },
      },
      actions: {
        findDeviceList: findDeviceList({commit}, params) {
            commit(types.SET_DEVICE_LIST, 'success')
        }
      },
   }
  },
})

output errpr:

[vuex] unknown local mutation type: SET_DEVICE_LIST, global type: common/SET_DEVICE_LIST

Steps to reproduce

when use vuex3.0 is ok, but vue4.0 use moduels is error, not to find commit path

What is expected?

how to vuex4.0 use modules the commit of mutation

ux-engineer commented 4 years ago

I'd like to add here that it would be great to have official examples for the modules with TypeScript as well.

@kiaking, in your recent conference talks you were showing Vuex shims with State type added... Could please review our working, but not yet fully right typed, example?

I just pieced together a working Github repo with a Codesandbox demonstration from previous examples done by the wider community, and what I found to be quirky about this approach: Using Vuex 4 modules in Vue 3 with TypeScript, and how to fix cyclical dependency linting error?

devin-huang commented 4 years ago

@ux-engineer thank you.

dlodeprojuicer commented 3 years ago

Also cant get it to work https://stackoverflow.com/questions/64349235/how-to-use-vuex-modules-in-vue-3

dlodeprojuicer commented 3 years ago

This answer worked for me. Change import * as types from './mutation-type' to import types from './mutation-type'

soerenmartius commented 3 years ago

I've posted a working, fully-typed example here: https://gist.github.com/soerenmartius/ad62ad59b991c99983a4e495bf6acb04

yaquawa commented 3 years ago

Hope there is an official way to do this.

soerenmartius commented 3 years ago

Hope there is an official way to do this.

Not sure about it. We might need to wait for Vuex5 to be released, since the maintainers mentioned already that they "aren't planning to rewrite the code base in TS for Vuex 3 or 4".

ChenYCL commented 3 years ago

same issues, here my way to achieve

// main.js
import { createApp } from "vue";
import App from "./App.vue";
import "./index.css";
import  store from './store/index';

const app = createApp(App);
store.forEach(({modelName,key})=>{
    app.use(modelName,key)
})
app.mount("#app");
// store/index.ts

import count,{key as countKey} from "./count";
import model,{key as modelKey} from "./model";

const arr = [
    {
        modelName:count,
        key:countKey
    },
    {
        modelName: model,
        key:modelKey
    }
]

export default arr;
// store/count.ts
import {InjectionKey} from 'vue';  // store namespace
import { createStore, useStore as baseUseStore, Store } from 'vuex'

export interface Props {
    count: number
}

export const key: InjectionKey<Store<Props>> = Symbol()

export default  createStore<Props>({

    state: {
        count:0
    },

    mutations: {
        increment(state) {
            state.count++
        },
        decrement(state) {
            state.count--
        }
    }
})

export function useStore () {
    return baseUseStore(key)
}
// demo.vue
<template>
  <h1>{{ msg }}</h1>
  <p>count is {{ count }}</p>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</template>

<script>
import {useStore} from "../store/count";
import {computed} from "vue";

export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },

  setup(){
    const store = useStore()
    return {
      count:computed(() => store.state.count),
      increment:()=>store.commit('increment'),
      decrement:()=>store.commit('decrement')
    }
  }

};
</script>
kiaking commented 3 years ago

Wait, I think we're losing focus on this issue but all you need to do is to remove namespaced: true. I don't think it works in Vuex 3 either 🤔

import { createStore } from 'vuex'
import * as types from './mutation-type'

export default createStore({
  modules: {
    common: {
      namespaced: true, // <- remove this
      state: {
        device: '',
      },
      mutation: {
        [types.SET_DEVICE_LIST](state: any, device: string) {
          state.device = device
        },
      },
      actions: {
        findDeviceList: findDeviceList({commit}, params) {
            commit(types.SET_DEVICE_LIST, 'success')
        }
      },
   }
  },
})

If I'm missing something, please provide a reproduction code 🙌

yaquawa commented 3 years ago

The vuex team may want to learn something from https://github.com/ktsn/vuex-smart-module

xieyezi commented 3 years ago

same problem with me,Now ,I resolve like this:

// store/modules/about/detail.ts

export interface AboutDetailState {
    title: string
    content: string
}

const state: AboutDetailState = {
    title: 'title from about detail state model.',
    content: 'content from about detail state model.'
}
const getters = {}
const mutations = {}
const actions = {}

export default {
    namespaced: true,
    state,
    getters,
    mutations,
    actions
}
// store/index.ts
import { createStore, createLogger } from 'vuex'
import modules from './modules'
import config from '../config'

const store = createStore({
    modules,
    state: {},
    mutations: {},
    actions: {},
    strict: config.isDev,
    plugins: config.isDev ? [createLogger()] : []
})

export default store

And then I use one Hooks to get state values:

import { useStore } from 'vuex'

const useVuexValue = (moduleName: string, storeKeys: Array<string>) => {
    let values: any = []
    const moduleNames = moduleName.split('/')
    const state = useCurry(moduleNames)
    storeKeys.forEach((storeKey) => {
        const value = state[storeKey]
        values.push(value ? value : null)
    })
    return values
}

const useCurry = (moduleNames: Array<string>) => {
    const store = useStore()
    let state = store.state
    moduleNames.forEach((moduleName) => {
        state = state[moduleName]
    })
    return state
}

export default useVuexValue

In the Views:

<template>
    <div class="about">
        <h1>This is an about page</h1>
        <p>{{ title }}</p>
        <p>{{ content }}</p>
    </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { useVuexValue } from '../hooks'

export default defineComponent({
    name: 'About',
    setup() {
        const [title, content] = useVuexValue('about/detail', ['title', 'content'])

        return {
            title,
            content
        }
    }
})
</script>

Which Like mapState, you only need to specify the module name and the required variable name.

u900x600 commented 3 years ago

I'd like to add that the type inference with modules does not work. To reproduce consider this code:

import {createStore, Module} from 'vuex';

type RootState = {
  appID: string
}

type SessionState = {
  lastSeen: number
}

const session: Module<SessionState, RootState> = {
  state: () => ({ lastSeen: -Infinity })
}

const store = createStore<RootState>({
  state: () => ({appID: 'r2d2'}),
  modules: {
    session
  }
});

// Property 'session' does not exist on type 'RootState'.
console.log(store.state.session);

In theory it should be possible to infer the »complete« type of the state, or am I just missing something here?

kiaking commented 3 years ago

@u900x600 You need to add types to the RootState.

type RootState = {
  appID: string
  session: SessionState
}
u900x600 commented 3 years ago

@kiaking Doing that requires the state: () => ({ … }) to include the Session part as well. So than it should look like that:

createStore<RootState>({
  state: () => ({
    appID: …,
    session: { … }
  })
})

But that should be done by the module.

zpliu1126 commented 3 years ago

it failed to me, mapState is work ok.

Vue@3.0 and Vuex@4.0.0

error message

vuex.esm-browser.js?5502:432 [vuex] unknown mutation type: main/increment

1. store Object

#! store Object
const store = new Vuex.Store({
  namespaced: true,
  modules: {

    main: {
      namespaced: true,
      state: {
        count: 0,
        size: 10,
      },
      mutations: {
        increment(state) {
          state.count++;
        },
      },
    },
  },
});

2. In the views

export default {
  name: "home_layout",
  data() {
    return {};
  },
  computed: {
    ...mapState({
      count: (state) => state.main.count,
      size: (state) => state.main.size,
    }),
  },
  methods: {
    ...mapMutations({
      increment: (commit)=> commit("main/increment"),
    }),
  },
}