小白学前端之TypeScript使用Vuex 4.0

简介

官方介绍:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

简单来说,Vuex 就像是前端的数据库或缓存,不管什么页面,只要 Vuex 里面有的数据,都可以去拿。

Vuex 分为 5 个部分:

  • State:是数据源,存放数据
  • Getters:可以取得 State 的数据,然后自定义组装返回新的数据
  • Mutations:可以改变 State 的数据,建议方法执行是同步的
  • Actions:可以异步执行 Mutations 里的方法
  • Modules:每个 Module 都有各自的 State、Getters、Mutations、Actions

这 5 个部分相辅相成。

TypeScript 使用

在 vue 项目根目录执行命令来进行 vuex 模块的安装

npm install [email protected] --save

安装好后我们新建文件 /src/store/store.ts ,然后在里面定义 InjectionKeyStore

import { InjectionKey } from 'vue'
import { createStore, useStore as baseUseStore, Store } from 'vuex'

// 定义 State 数据类型的接口
interface IState{
}

// 类型传递
export const key: InjectionKey<Store<IState>> = Symbol()

export const store = createStore<IState> ({ 
})

// 用于组合式API setup() 里,省的每次都传入 key 
export function useStore() {
    return baseUseStore(key)
}

然后在 main.ts 文件里使用上面定义的 vuex

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

createApp(App)
.use(store,key)
.mount('#app')

State

State 是存储数据源的地方,所以我们可以在这里存储我们的数据,比如我这边定义一个 name 字段,需要在接口 IState 添加定义数据类型

interface IState{
    name: string
}

然后在 createStore 里添加数据

export const store = createStore<IState> ({ 
    state:{
        name: 'ooooooh灰灰'
    }
})

数据我们已经定义好了,接下来就是要在页面访问这个数据了,下面提供了两种方式来访问 vuex 里的数据

组合式 API 访问

在组合式 API 中,我们可以直接导入刚才在 /src/store/store.ts 里定义的 useStore() 方法来访问 vuex 里的数据

import { defineComponent } from 'vue';
import { useStore } from './store/store'

export default defineComponent({

  setup(){
    let store = useStore()
    // 访问 state 里的 name 数据
    console.log(store.state.name)
  }
});

运行代码的话就会在控制台打印 ooooooh灰灰

…toRefs() 访问所有字段

如果要在页面访问的话,可以利用 ...toRefs() 来直接展开 store.state 里的所有字段,然后在页面直接访问 vuex 的 state 里的字段

// App.vue
<template>
  <div>
    {{ name }}
  </div>
</template>

<script lang="ts">
  import { defineComponent, toRefs} from 'vue';
  import { useStore } from './store/store'

  export default defineComponent({
    
    setup(){
      let store = useStore()
    
      return {
        // 展开 state 所有的字段
        ...toRefs(store.state)
      }
    }
  });
</script>

<style>

</style>

reactive 聚合单个字段

如果你想单个数据导入的话,可以直接和页面数据一起放在 reactive

import { defineComponent, reactive, toRefs} from 'vue';
  import { useStore } from './store/store'

  export default defineComponent({
    
    setup(){
      let store = useStore()
    	// 把 vuex 的 state 的数据放进 reactive 里
      let params = reactive({
        name: store.state.name
      })
      return {
        ...toRefs(params),
      }
    }
  });

computed 访问单个字段

也可以使用 computed 模块来访问数据,要先导入 vue 里的 computed

// App.vue
<template>
  <div>
    {{ name }}
  </div>
</template>

<script lang="ts">
  import { defineComponent, computed} from 'vue';
  import { useStore } from './store/store'

  export default defineComponent({
    
    setup(){
      let store = useStore()
      
      return {
        name: computed(()=>store.state.name)
      }
    }
  });
</script>

<style>

</style>

Getters

getters 里的方法在 vuex/types/index.d.ts 中是这样定义的

export type Getter<S, R> = (state: S, getters: any, rootState: R, rootGetters: any) => any;

他有 4 个参数,分别是 state、getters、rootState、rootGetters

其中,state 可以取得同级中 state 里的数据,getters 可以取得同级中 getters 其他的方法返回的数据

rootStaterootGetters 是在当当前 Getters 处于 module 中时,可以取得根部的 state 和 gatters 里的数据

比如我们可以将 state 里的变量封装成一句话然后返回:

export const store = createStore<IState> ({ 
    state:{
        name: 'ooooooh灰灰',
    },
    getters:{
        newName(state):string{
          	// 通过 state 访问 name 字段
            return '大家好!我是:'+state.name
        }
    }
})

当我们要访问其他 getter 时,我们可以这样:

export const store = createStore<IState> ({ 
    state:{
        name: 'ooooooh灰灰',
        age: 20
    },
    getters:{
        hello(state,getters):string{
          	// 通过 getters 访问其他 getter
            return '大家好!我是:'+state.name+','+getters.ageInfo
        },
        ageInfo(state):string{
            return '年龄:'+state.age
        }
    }
})

组合式 API 访问

我们可以在组合式 API 里像访问 state 的里数据一样访问 gatters 里的方法:

import { defineComponent } from 'vue';
import { useStore } from './store/store'

export default defineComponent({

  setup(){
    let store = useStore()
    // 访问 getters 里的 hello 方法
    console.log(store.getters.hello)
  }
});

此外,getters 也可以使用 ...toRefs()computed 这些方法来访问:

<template>
  <div>
    {{ hello }}
  </div>
</template>

<script lang="ts">
  import { defineComponent, computed, toRefs } from 'vue';
  import { useStore } from './store/store'

  export default defineComponent({
    setup(){
      let store = useStore()
      return {
        // 通过 computed 访问 getters 里的 hello 
        hello: computed(()=>store.getters.hello),
        
        // 通过 ...toRefs() 访问
        // ...toRefs(store.getters),
      }
    }
  });
</script>

<style>

</style>

Mutations

如果你要改变 state 里的数据时,就要用到 Mutations 了,它可以提供改变 state 里数据的方法,它在 vuex/types/index.d.ts 中是这样定义的:

export type Mutation<S> = (state: S, payload?: any) => any;

其中 state 可以拿到 state 里的数据,payload 是自定义传入的参数,后面有个问号,代表这是可选项

所以当我们要改变 state 的字段的值时,我们可以在 store.ts 中这样写代码 :

export const store = createStore<IState> ({ 
    state:{
        name: 'ooooooh灰灰',
    },
    mutations:{
        changeName(state){
            // 改变 state 中 name 的值
            state.name = 'greycode'
        }
    }
})

如果要自定义传入参数的话,就可以这样写:

export const store = createStore<IState> ({ 
    state:{
        name: 'ooooooh灰灰',
    },
    mutations:{
        changeName(state,newName:string){
            // 传入自定义字段并设置
            state.name = newName
        }
    }
})

组合式 API 访问

在组合式 API 中,我们可以用 commit 来提交执行这个方法:

import { defineComponent } from 'vue';
import { useStore } from './store/store'

export default defineComponent({

  setup(){
    let store = useStore()
    let change = () => {
      // 提交执行 mutations 中 changeName 方法
      // store.commit('changeName')
      
      // 提交执行 mutations 中 changeName 方法,并传入自定义参数
      store.commit('changeName','自定义的')
    }
    return {
      change
    }
  }
});

…mapMutations

我们可以直接在组合式 API 中使用 ...mapMutations 来获得 mutations 中的方法,然后直接在页面中调用这个方法

  import { defineComponent } from 'vue';
  import { mapMutations } from 'vuex';
  import { useStore } from './store/store'

  export default defineComponent({
    
    setup(){
      let store = useStore()
      return {
        // 使用 ...mapMutations 来获得 mutations 中的方法
        ...mapMutations(['changeName'])
      }
    }
  });

然后直接在页面中使用:

<template>
  <div>
    <button type="button" @click="changeName">按钮</button>
    
    <!-- 也可以传入函数自定义参数 -->
    <button type="button" @click="changeName(’自定义名字‘)">按钮</button>
  </div>
</template>

Action

当要异步改变 state 中的数据时,就要用到 Action 了,但是它不是直接改变 state 中的数据,而是通过异步执行 mutations 中的方法来间接改变 state 中的数据的

它在 vuex/types/index.d.ts 中是这样定义的:

export type Action<S, R> = ActionHandler<S, R> | ActionObject<S, R>;

它支持两种类型的数据,一个是 ActionHandler<S, R> ,另一个是 ActionObject<S, R>。其中 ActionObject 一般用于 Module 中的命名空间,它们的定义如下:

export type ActionHandler<S, R> = (this: Store<R>, injectee: ActionContext<S, R>, payload?: any) => any;

export interface ActionObject<S, R> {
  root?: boolean;
  handler: ActionHandler<S, R>;
}

这里只讲下 ActionHandler ,另外一个等到 Module 模块中再讲。

在 ActionHandler 中,它有 3 个参数,分别是 this、injectee、payload,其中 this 代表的是整个 Store 对象,injectee 是当前 Action 所在的上下文,payload 是可以自定义的传入参数

所以我们可以这样使用它:

export const store = createStore<IState> ({ 
    state:{
        name: 'ooooooh灰灰'
    },

    mutations:{
        changeName(state){
            state.name = '异步改名'
        }
    },
    actions:{
        asyncChange(ctx){
          // 两秒后更改名字
          setTimeout(() =>{
            ctx.commit('changeName')
          },2000)
        }
    }
})

组合式 API 访问

定义好 actions 后,我们可以在组合式 API 中用 dispatch 来分发 action:

import { defineComponent } from 'vue';
import { useStore } from './store/store'

export default defineComponent({

  setup(){
    let store = useStore()
    let syncChange = () => {
      // 执行 actions 中的 asyncChange 方法
      store.dispatch('asyncChange')
    }
    return {
      syncChange
    }
  }
});

…mapActions

也可以用 ...mapActions 来直接获得 actions 中的方法:

import { defineComponent } from 'vue';
import { mapActions } from 'vuex';
import { useStore } from './store/store'

export default defineComponent({

  setup(){
    let store = useStore()
    return {
      ...mapActions(['asyncChange'])
    }
  }
});

页面使用的话和 mutation 差不多,直接访问 actions 中的方法名就可以了:

<template>
  <div>
    <button type="button" @click="asyncChange">按钮</button>
  </div>
</template>

最后

除此之外还有一个 Module 模块,不过一般小项目用不到而且内容也比较多,下次再学吧。