1. Vuex的基本使用

vuex的安装:

npm i vuex

vuex的配置,@/store/index.js:

import {createStore} from 'vuex'//导入createStore构造函数
export default createStore({ 
    state:{ //Vuex的状态,实际上就是存数据的地方
        person:{
            name:'jack',
            age:200
        }
    },
    getters:{ //提供获取Vux状态的方式, 注意在组件中调用时getPerson是以属性的方式被访问
        getPerson(state){
            return state.person
        }
    },
    mutations:{ //提供直接操作Vuex的方法,注意mutations里的方法中不能有任何异步操做
        ageGrow(state, value){
            //第一个参数state为Vuex状态;第二个参数为commit函数传来的值
            state.person.age += value
        }
    },
    actions:{ //提供通过mutations方法来简介操作Vuex的方法
        ageGrow(context, value){ 
            //第一个参数context为上下文,提供一些方法;第二个参数为dispatch函数传来的值
            context.commit('ageGrow', value)
        }
    }, 
})

在@/main.js中引入:

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')

在组件中使用Vuex:

<template>
    <h1>名字:{{person.name}}--年龄:{{person.age}}</h1>
    <input type="text" v-model="value">
    <button @click="ageGrow(value)">增加年龄</button>
    <!-- 在input框输入一个数字,点击按钮,可以看到年龄发生变化,说明Vuex正常工作 -->
</template>

<script>
    import {useStore} from 'vuex' 
    import {ref} from 'vue'
    export default {
        setup(){
            const store = useStore()    //获取store对象
            let person = store.getters.getPerson    //从组件中获取状态(数据)person 方式一
            // let person = store.state.person      //从组件中获取状态(数据)person 方式二
            let value = ref('输入年龄的增量')
            function ageGrow(ageGrowth){
                ageGrowth = parseInt(ageGrowth)
                if(isNaN(ageGrowth)){
                    ageGrowth = 0
                }
                store.dispatch('ageGrow', ageGrowth)
                //通过dispatch来调用actions里的'ageGrow'方法,参数为ageGrowth
               	//actions的方法又会通过commit来调用mutations里的方法,从而引起状态(数据)的变化
               	//也可以在组件里跳过dispatch actions,直接store.commit
            }
            return {
                person, 
                value,
                ageGrow
            }
        }
    }
</script>
<style></style>

把代码复制粘贴到脚手架中运行,可以看到直观的效果,有助于理解。

小结:安装完vuex之后,首先要用creatRouter构造函数创建一个router对象,并在main.js中引入这个对象。然后在组件中,通过userStore方法来获取这个router对象,进一步通过getter或者state可以得到Vuex状态(数据),通过dispatch->actions->mutations->state的数据传送方式可以操作和改变Vuex的状态(数据)

2. Module

Vuex官方原话:“由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。”

什么叫单一状态树呢,其实就是上文中的state对象。在Vuex的基本使用中,我们使用state对象来存储Vuex的状态,state对象里面可以嵌套其他的对象,它是一个树形的结构,而且这个state对象是唯一的,所有的状态都要存储在这一个对象里。因此,我们称之为单一状态树。
这种单一状态树的弊端是显而易见的,对于中大型项目来说,要托管给Vuex的状态有很多,把这些海量的数据如果都塞到一个文件里面的一个对象里面,未免显得过于臃肿,不管是开发起来还是维护起来都会有很多不变。
对此,官方给出了解决方案:

“为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割”

2.1 vuex中模块化的基本使用

vuex的模块化没什么难理解的,就是把store给拆开,一个对象拆成多个对象,一个文件拆成多个文件,并且彼此可以拥有独立的命名空间。
道理很简单,所以直接看样例:

文件结构

├──src
   ├── components
   │   └── Test.vue
   └── store
       ├── index.js
       └── modules
           ├── male.js
           └── female.js

index.js

import {createStore} from 'vuex'
import female from './modules/female'   //导入模块
import male from './modules/male'       //导入模块
export default createStore({ 
    modules:{   //使用模块
        female,
        male
    }
})

male.js

export default {
    namespaced:true,    //namespaced:true代表该模块带有独立命名空间
    state:{             //否则,默认是处于全局命名空间,就和非模块化一样
        personList:[
            {name:'张飞', id:'004'},
            {name:'武则天', id:'005'},
            {name:'秀吉', id:'006'},
        ]
    },
    mutations:{
        addMutation(state, value){      //往personList中添加一个人
            state.personList.push(value)
        },
        removeMutaion(state, value){    //往personList中删除一个人
            state.personList = state.personList.filter((el) => el.id != value.id)
        }
    },
    actions:{
        addAction(context, value){
            setTimeout(() => {
                context.commit('addMutation', value) // ->'male/addMutation'
            }, 1000);
        },
        removeAction(context, value){
            context.commit('removeMutaion', value)
        }
    },
    getters:{
        personList(state){
            return state.personList
        }
    }
}

female.js

export default {
    namespaced:true,    //namespaced:true代表该模块带有独立命名空间
    state:{             //否则,默认是处于全局命名空间,就和非模块化一样
        personList:[
            {name:'李白', id:'001'},
            {name:'孙尚香', id:'002'},
            {name:'大乔', id:'003'},
        ]
    },
    mutations:{
        addMutation(state, value){      //往personList中添加一个人
            state.personList.push(value)
        },
        removeMutaion(state, value){    //往personList中删除一个人
            state.personList = state.personList.filter((el) => el.id != value.id)
        }
    },
    actions:{
        addAction(context, value){
            setTimeout(() => {
                context.commit('addMutation', value) // ->'female/addMutation'
            }, 1000);
        },
        removeAction(context, value){
            context.commit('removeMutaion', value)
        }
    },
    getters:{
        personList(state){
            return state.personList
        }
    }
}

Test.vue

<template>
    <h1>女人:</h1>
    <li v-for="femalePerson in femalePersons" :key="femalePerson.id">
        {{femalePerson.name}}
        <button @click="addToMale(femalePerson)">添加到男人</button>
    </li>
    <h1>男人:</h1>
    <li v-for="malePerson in malePersons" :key="malePerson.id">
        {{malePerson.name}}
        <button @click="addToFemale(malePerson)">添加到女人</button>
    </li>
    <!-- 有两个列表,分布是男人和女人,通过点击按钮可以把列表中的某些项添加到另一个列表中 -->
    <!-- 建议粘贴复制并运行代码,这样更直观 -->
</template>

<script>
    import { computed } from '@vue/runtime-core';
    import {useStore} from 'vuex'
    export default {
        setup(){
            let store = useStore()
            let malePersons = computed(() => store.getters['male/personList']) //通过getter获取state
            let femalePersons = computed(() => store.state.female.personList)   //直接获取state
            function addToMale(person){
                store.dispatch('male/addAction', person)
                store.dispatch('female/removeAction', person)
                //如果模块中namespaced === true,那么要在方法名前面添加模块的逻辑路径
                //index.js里使用的模块为路径的起点。
                //比如index里面有一个moduleA,moduleA有一个子模块moduleB,module有一个action是actionx
                //那么调用方式为 store.dispatch('moduleA/moduleB/actionx', value)
            }
            function addToFemale(person){
                store.dispatch('female/addAction', person)
                store.dispatch('male/removeAction', person)
            }
            return {
                malePersons,
                femalePersons,
                addToMale,
                addToFemale
            }
        }
    }
</script>
<style></style>

2.2 在命名空间中访问全局内容

什么是全局内容?不在同一个模块中的内容就是全局内容。
比如,对于上文中的female模块来说,male模块中的getters state action mutations就是全局内容,接下来将会讲解如何在一个模块中访问到全局内容。
为了便于理解,我创造了一个新的样例:

├──src
   ├── components
   │   └── Test.vue
   └── store
       ├── index.js
       └── modules
           ├── moduleA.js
           └── moduleB.js

index是所有的模块的根,moduleA和moduleB是两个子模块,接下来要做的事情就是在index.js、moduleA.js、moduleB.js中写一些getters state action mutations,最终达成的效果是在index中访问moduleA的内容,在moduleA中访问moduleB的内容,在moduleB中访问index的内容。

index.js:

import {createStore} from 'vuex'
import moduleA from './modules/moduleA'   //导入模块
import moduleB from './modules/moduleB'       //导入模块
export default createStore({ 
    state:{info:{name:'root', num:100}},
    getters:{
        state: state => state,
        moduleAStateThroughRoot: (state, getters, rootState, rootGetters) => {
            //在getter中访问全局内容
            //rootState为根状态,rootGetters为根getters,这两个属性是访问全局内容的工具
            //通过模块A的来获得root的状态

            //因为这里本身就是模块的根,所以这里的state和rootState、getters和rootGetters是一个东西
            // console.log(state === rootState, getters === rootGetters); // true true

            return rootGetters['moduleA/state']
            // return rootState.moduleA
        }
    },
    actions:{addAction: (context, value) => {context.commit('moduleA/addMutation', value)}},
    mutations:{addMutation: (state, value) => {state.info.num += value}},
    modules:{   //使用模块
        moduleA,
        moduleB
    }
})

moduleA.js:

export default {
    namespaced :true,
    state:{info:{name:'moduleA', num:200}},
    getters:{
        state: state => state,
        moduleBStateThroughModuleA: (state, getters, rootState, rootGetters) => {
            //在getter中访问全局内容
            //rootState为根状态,rootGetters为根getters,这两个属性是访问全局内容的工具
            //通过模块B的来获得模块A的状态

            // return rootState.moduleB
            return rootGetters['moduleB/state']
        }
    },
    actions:{
        addAction: (context, value) => {context.commit('moduleB/addMutation', value, {root:true})},
        //context中包含dispatch, commit, getters, rootGetters
        //在action中调用全局mutation,只需要在commit函数中添加第三个参数{root:true},这样第一个参数就会被当作全局内容来解析。
        //在action中调用全局action,与上述同理。
        //同时,还可通过rootGetters访问全局getters
        
        globalAction:{
            root: true, //添加root:true属性,即可把globalAction注册为全局action,具体内容写在handler中
            handler: (context, value) => {      //虽然是全局action,但参数中的上下文context仍然是局部的
                alert(context.state.info.name)  //弹出应该是moduleA
            }
        }
    },
    mutations:{addMutation: (state, value) => {state.info.num += value}},
}

moduleB.js

export default {
    namespaced :true,
    state:{info:{name:'moduleB', num:300}},
    getters:{
        state: state => state ,
        rootStateThroughModuleB:(state, getters, rootState, rootGetters) => {
            //在getter中访问全局内容
            //rootState为根状态,rootGetters为根getters
            //通过root的来获得模块B的状态

            return rootGetters['state']
            // return rootState
        }
    },
    actions:{addAction: (context, value) => {context.commit('addMutation', value, {root:true})}},
    //context中包含dispatch, commit, getters, rootGetters
    //在action中调用全局mutation,只需要在commit函数中添加第三个参数{root:true},这样第一个参数就会被当作全局内容来解析。
    //在action中调用全局action,与上述同理。
    //同时,还可通过rootGetters访问全局getters
    mutations:{addMutation: (state, value) => {state.info.num += value}},
}

Test.vue:

<template>
    <li> {{rootModule.name}}---{{rootModule.num}} <button @click="rootClick">rootAction---A++</button> </li>
    <li> {{moduleA.name}}---{{moduleA.num}} <button @click="aClick">moduleAction---B++</button> </li>
    <li> {{moduleB.name}}---{{moduleB.num}} <button @click="bClick">moduleAction---root++</button> </li>
    <!-- 点击root,moduleA数字加一;点击moduleA,moduleB数字加一;点击moduleB,root数字加一 -->
    <button @click="store.dispatch('globalAction')">触发全局action</button>
</template>

<script>
    import {useStore} from 'vuex'
    export default {
        setup(){
            let store = useStore()
            console.log(store.getters);
            let rootModule = store.getters['moduleB/rootStateThroughModuleB'].info //通过moduleB的getters获得root的状态
            let moduleA = store.getters['moduleAStateThroughRoot'].info            //通过root的getters获得moduleA的状态
            let moduleB = store.getters['moduleA/moduleBStateThroughModuleA'].info //通过moduleA的getters获得moduleB的状态
            // let moduleB = store.state.moduleB.info
            // let moduleA = store.state.moduleA.info
            // let rootModule = store.state.info
            function rootClick(){store.dispatch('addAction', 1)}    //调用root中的action,改变的是moduleA的状态
            function aClick(){store.dispatch('moduleA/addAction', 1)}//调用moduleA中的action,改变的是moduleB的状态
            function bClick(){store.dispatch('moduleB/addAction', 1)}//调用moduleB中的action,改变的是root的状态
            return {rootModule,moduleA,moduleB,rootClick,aClick,bClick,store}
        }
    }
</script>
<style></style>

在模块中使用vuex

// import { State } from "@/type/test";
// import { useStore } from "vuex";
// const store = useStore()

/* 
    在模块中使用vuex时,上面的方式得到的store.state是undefined,下面这种方式才能正常得到state
    不知道是什么原因
*/


import store from '../store'
export function increase(){
    console.log(store.state)
}

3. vuex的typescript用法

vuex的typescript用法其实就是把state加上ts里的类型限制

3.1 不使用模块化

store文件

// store.ts
import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'

// 为 store state 声明类型
export interface State {
  count: number
}

// 定义 injection key
export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
  state: {
    count: 0
  }
})

在main.ts里引入

// main.ts
import { createApp } from 'vue'
import { store, key } from './store'

const app = createApp({ ... })

// 传入 injection key
app.use(store, key)

app.mount('#app')

在组件中使用useStore()获取store,将上述 injection key 传入 useStore 方法可以获取类型化的 store。

// vue 组件
import { useStore } from 'vuex'
import { key } from './store'

export default {
  setup () {
    const store = useStore(key)

    store.state.count // 类型为 number
  }
}

在每个组件中都导入key有些重复且麻烦,我们可以将useStore()封装成myUseStore()

// 在store.ts增加下面几行
import { useStore } from 'vuex'
export function myUseStore () {
  return useStore(key)
}

3.2 使用模块化

store/index.ts

import { createStore, Store  } from 'vuex'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'
import { InjectionKey } from 'vue'

//模块A state的类型
interface moduleAState {
	name:string,
	age:number
}

//模块B state的类型
interface moduleBState {
	id:string,
	adult:boolean
}

//vuex state类型
interface State{
	moduleA:moduleAState,
	moduleB:moduleBState 
}


//如果使用模块化的话,在createStore参数里面就不要写state了,否则会报错
export const key: InjectionKey<Store<State>> = Symbol()
export const store =  createStore<State>({
  // state:{}, //不可以加这一行,否则报错
  modules: {
    moduleA,
    moduleB
  }
})

在组件中访问:

// vue 组件
import { useStore } from 'vuex'
import { key } from './store'

export default {
  setup () {
    const store = useStore(key)

    store.state.moduleA// 类型为 moduleAState
    store.state.moduleB// 类型为 moduleBState
  }
}

待更新

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐