万字警告!一篇搞定状态管理工具 (Vuex 、 Pinia )

113 阅读6分钟

前言

本文适合有前端基础有一定使用库经验jym 快速查阅,并不适合纯小白观看阅读(因为小白体验差)!!!

什么是状态管理?

状态管理指的是在多个组件有效的管理和维护数据的状态,也就是说在需要数据在多个组件之间共享和传递的时候,我们就是可以使用状态管工具来保证数据的一致性可追踪性可预测性

1. Vuex

1.1 核心概念

1.1.1 State(状态)

作用:保存需要共享的数据,类似组件中的data

Vue2 中

方法一:在需要的组件中使用 this.$store.state 获取状态对象下的数据

方法二:使用辅助函数 mapState
import { mapState } from 'vuex';

computed: {
    // 使用 mapState 将状态映射到组件的计算属性中
    // 接收的是一个数组,要传入 state 中多个状态值的话要以逗号分隔
    ...mapState(['hobby'])
    
    //另一种写法
    ...mapState({
          'hobby': state => state.hobby
        })
  },
  
// 最后可以直接在模板中使用
<template>
  <div>
    <p>我的爱好是: {{ hobby }}</p>
  </div>
</template>Vue3 中
方法一:在需要的组件中使用 store.state 获取状态对象下的数据
import { useStore } from 'vuex';

const store = useStore();
const hobby = store.state.hobby;
})

同理:
<script setup>
import { useStore } from 'vuex';
import { computed } from 'vue';

const store = useStore();
const hobby = computed(() => {
    return store.state.hobby;
})

方法二:使用辅助函数 mapState
<script setup> 
import { useStore } from 'vuex'; 
import { computed } from 'vue'; 

const store = useStore(); 
// 使用 mapState 将状态映射到组件的计算属性中 
const { hobby } = mapState(['hobby']); 

</script>

// 最后可以直接在模板中使用
<template>
  <div>
    <p>我的爱好是: {{ hobby }}</p>
  </div>
</template>

1.1.2 Getters(获取器)

作用:相当于计算属性,可以对state 进行计算、过滤、转换,并保持响应式

Vue2 中
方法一:在需要的组件中使用 this.$store.getters 获取

方法二:使用辅助函数 mapGetters
import { mapGetters } from 'vuex';

 computed: {
    ...mapGetters(['getHobby'])
  }

// 最后可以直接在模板中使用
<template>
  <div>
    <p>我的爱好是: {{ getHobby }}</p>
  </div>
</template>

在 Vue3 中
方法一:在需要的组件中使用 store.getters 获取
import { useStore } from 'vuex';

const store = useStore();
const hobby = store.getters.getHobby
})

同理:
import { useStore } from 'vuex';
import { computed } from 'vue';
const store = useStore();
const hobby = computed(() => {
    return store.getters.getHobby;
})

方法二:使用辅助函数 mapGetters
<script setup> 
import { useStore, mapGetters } from 'vuex'; 

const store = useStore(); 

// 不改变 getter 属性名的方式 
const { getHobby } = mapGetters(['getHobby']); 

// 将 getter 属性取另外一个名字的方式 
const { getHobby: getHobbyVal } = mapGetters({ getHobby:'getHobbyVal'});

</script>
  
  // 最后可以直接在模板中使用(使用另取名字的方式)
<template>
  <div>
    <p>我的爱好是: {{ getHobbyVal }}</p>
  </div>
</template>

1.1.3 Mutations(突变)

作用:同步修改 state 的值(使用 commit 调用)

Vue2 中
方法一:直接使用 $store.commit 传入额外的参数
this.$store.commit('updateHobby','小军')

方法二:使用 mapMutations 辅助函数
import { mapMutations } from 'vuex'

methods: {
    // 映射到组件的方法中,接受的参数是一个数组,多个方法以逗号分隔
    ...mapMutations(['updateHobby']),
    // 将 updateHobby 映射另外一个名字的方式
    ...mapMutations({
          setHobby: 'updateHobby'
        }),
    // 在其他要使用的方法中
    otherMethod(){
        this.updateHobby('小军')
    }
  }

在 Vue3 中
方法一:直接使用 store.commit 传入额外的参数
import { useStore } from 'vuex';

const store = useStore();
store.commit('updateHobby','小军')

方法二:使用 mapMutations 辅助函数
<script setup>
import { mapMutations } from 'vuex';

// 使用 mapMutations 接受的参数是一个数组,多个方法以逗号分隔
const updateHobby  = mapMutations(['updateHobby']);
// 将 updateHobby 映射另外一个名字的方式
const updateHobby  = mapMutations({ setHobby:'updateHobby'});


// 在其他方法中直接调用
function otherMethod() {
  setHobby('小军');
}
</script>

1.1.4 Actions(动作)

作用:异步修改 state 的值(使用 dispatch 调用)

Vue2 中
方法一:直接使用 $store.dispatch 触发
this.$store.dispatch('asyncUpdateHobby','小军')

方法二:使用 mapActions 辅助函数
import { mapActions } from 'vuex'

methods: {
    // 使用 mapActions 接受的参数是一个数组,多个方法以逗号分隔
    ...mapActions(['asyncUpdateHobby']),
    // 将 updateHobby 映射另外一个名字的方式
    ...mapActions({
      asyncSetHobby: 'asyncUpdateHobby'
    }),
    // 在其他方法中直接调用
    otherMethod(){
      asyncUpdateHobby('小军')
    }
  }

在 Vue3 中
方法一:直接使用 store.dispatch 触发
import { useStore } from 'vuex';

const store = useStore();
store.dispatch('asyncUpdateHobby','小军')

方法二:使用 mapActions 辅助函数
<script setup>
import { mapActions } from 'vuex';

// 使用 mapActions 接受的参数是一个数组,多个方法以逗号分隔
const asyncUpdateHobby  = mapActions(['asyncUpdateHobby']);

// 将 asyncUpdateHobby 映射另外一个名字的方式
const asyncUpdateHobby  = mapActions({ asyncSetHobby:'asyncUpdateHobby'});

// 在其他方法中直接调用
function otherMethod() {
  asyncSetHobby('小军');
}
</script>

1.1.5 Modules(模块)

作用:将应用的状态分成不同模块,使得所有模块状态独立,避免 store 对象臃肿

Vue2 中
使用 modulesA 下的 getters 的 xxxx
// a 是 store 下更改的 modulesA 对应的名称
this.$store.getters['a/xxxx']

使用 modulesB 下的 actions 的 xxxx
this.$store.dispatch('b/xxxx', 123);

在 Vue3import { useStore } from 'vuex';
import { computed } from 'vue';

const store = useStore();

// 使用模块A下的getters
const moduleAGetter = computed(() => store.getters['a/xxxx']);

// 使用模块B下的actions 
const moduleBAction = () =>{
    store.dispatch('b/xxxx', 123);
  }

1.2 Vue2写法(配置 Vuex)

1.2.1 自定义的模块(不使用时 modules 可忽略)

`src/store/modules/moduleA.js`const state = { ...... };

const getters = { ...... };

const mutations = { ...... };

const actions = { ...... };

export default {
  namespaced: true,//开启命名空间
  state,
  mutations,
  actions,
  getters,
};

在 `src/store/modules/moduleB.js`中
  与 moduleA 同上省略...

1.2.2 在 store 文件夹下的 index.js

import Vue from 'vue';
import Vuex from 'vuex';
//需要使用 modules 的时候
import moduleA from './modules/moduleA'; 
import moduleB from './modules/moduleB';

// 注册 Vuex
Vue.use(Vuex);

const state = {
  hobby:'敲代码'
};

const getters = {
  getHobby(state) {
    return state.hobby;
  },
};

const mutations = {
  updateHobby(state,payload) {
     // payload指的是传递的数值
     // 大多时候应该为一个对象,因为可以包含多个字段
    state.hobby = payload;
  },
};

const actions = {
  asyncUpdateHobby(ctx, payload) {
    // ctx指的是上下文对象context
    ctx.commit("updateHobby", payload);
  },
  
    // 第二种方式(参数解构)
  asyncUpdateHobby({ commit, state, dispatch}, payload){
    commit('updateHobby',payload);
    dispatch('otherAsyncFn',payload);
  }
};
export default new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  // 需要使用 modules 的时候
  modules:{
   a:moduleA,// 更改模块名
   b:moduleB // 更改模块名
  }
});

1.2.3 在 main.js 中

import Vue from 'vue' 
import store from 'store/index.js'

new Vue({ 
    el:'#app', 
    router, 
    store,
    render:h=>h(App) 
})

1.3 Vue3写法(配置 Vuex)

1.3.1 自定义的模块(不使用时 modules 可忽略)

子模块的配置相同,具体内容如上(1.2.1)

1.3.2 在 store 文件夹下的 index.js

import { createStore } from "vuex";
//需要使用 modules 的时候
import moduleA from './modules/moduleA'; 
import moduleB from './modules/moduleB';

// 创造store实例
const store = createStore({
  state() {
    return {
       hobby:"敲代码",
    };
  },
  getters: {
    getHobby(state) {
      return state.hobby;
    },
  },
  mutations: {
    updateHobby(state, payload) {
      // payload指的是传递的数值
      // 大多时候应该为一个对象,因为可以包含多个字段
      state.hobby = payload;
    },
  },
  actions: {
    asyncUpdateHobby(ctx, payload) {
      // ctx指的是上下文对象context
      ctx.commit("updateHobby", payload);
    },
  },
  // 需要使用 modules 的时候
  modules:{
   a:moduleA,// 更改模块名
   b:moduleB // 更改模块名
  }
});

export default store

1.3.3 在 main.js 中

import App from "./App";
import router from "./router"; 
import { createApp } from "vue";
//引入vuex
import store from 'store/index.js'

const app = createApp(App); 
app.use(router).use(store).mount("#app");

2. Pinia

注:该部分仅演示 Vue3 下的代码

2.1 配置 store

/** src/store/index.ts **/
import { createPinia } from 'pinia';

// 要实现 存储状态并持久化 可安装并导入插件
import piniaPluginPersistedstate from  "pinia-plugin-persistedstate";

const store = createPinia();

store.use(piniaPluginPersistedstate); //不用插件可忽略

export default store

/** src/main.ts **/
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';

const app = createApp(App);
app.use(store);

/** src/store/info.js **/
import { defineStore } from 'pinia';

// defineStore 第一个参数:唯一id 第二个参数:配置对象或 setup 函数
export const xxxStore = defineStore('useInfoStore',{
    state: () => {
        return {
            hobby: '敲代码',
            age: 23
        }
    },
    getters:{
    // 也可以直接使用 this 访问其他的 getter 属性
        double:(state) => state.age * 2,
    },
    // pinia 中已经取消了 mutations, 统一使用 actions
    // 在 actions 中 同步异步都可以,也可以在这里调用 API 和其他 action 方法
    actions: {
        setHobby(val) {
            // 通过this访问整个 store 实例
            this.hobby = val;
        },
    },
    // 持久化配置 (直接设置为 true 表示所有状态数据持久化) 
    persist: { 
    // 修改当前 Store 的 id 
    key: 'storekey', 
    // 修改为 sessionStorage,默认为 localStorage 
    storage: window.sessionStorage, 
    // 持久化状态路径
    // 通过点符号路径,指定缓存的字段,默认是所有状态数据 
    paths: ['hobby'], 
    },
})

// 注:若传递给 defineStore 的是 setup 函数
// 那么 ref()就是 state 属性
// computed()就是 getters
// function()就是 actions

2.2 核心概念

2.2.1 State(状态)

作用:存储仓库中的数据,当状态发生改变的时候,相关联的组件自动更新

<script setup>
// 引入自定义的 store
import { useInfoStore } from "@/store/info"; 
// 需要解构时
import { storeToRefs } from 'pinia';
// 实例化
const info = useInfoStore();

/** 修改 **/
方法一:直接修改
info.hobby = '唱歌';

方法二:调用 $patch(可同时修改多个值)
info.$patch({
  hobby:'唱歌'
})

// 在需要进行额外操作来实现修改的情况下,推荐传递一个函数
info.$patch(state =>{
  // ......
})

/** 重置(恢复为初始值) **/
info.$reset();

/** 解构(直接解构会失去响应式) **/
const { hobby } = storeToRefs(info);

/** 替换(覆盖原有状态) **/
info.$state = { 
 hobby:'唱歌'
}
</script>

/** 访问(模板上) 无需辅助函数**/
<template>
    <div>
       <p>我的爱好还是{{info.hobby}}哦</p>
    </div>
</template>

2.2.2 Getter(获取器)

作用:从状态派生计算属性的方法

<script setup>
// 引入自定义的 store
import { useInfoStore } from "@/store/info"; 

// 实例化
const info = useInfoStore();

/** 访问--- 跟state属性完全一样 **/

/** 使用其他 Store 的 Getter **/
// 引入其他的 store
import { useOtherStore } from "@/store/other";
// 实例化
const other = useOtherStore();

</script>

2.2.3 Action(操作)

作用:修改状态的方法,可以执行异步操作,并且可以做直接使用 this 来访问仓库中的状态和其他操作

<script setup> 
// 引入自定义的 store
import { useInfoStore } from "@/store/info"; 

const store = useInfoStore(); 
// 直接调用
store.setHobby('唱歌'); 

</script> 

/** 模板上使用 **/
<template> 
    <button @click="store.setHobby('唱歌')">修改爱好</button> 
</template>

2.2.4 Plugin(插件)

作用:包装 or 添加 新的属性方法

/** 添加 **/
import { createPinia } from 'pinia';

const store = createPinia();
//通过 use() 添加到实例
store.use(xxx);

/** 扩展 **/
// 给每个 store 都添加上特定属性
store.use(({store})=>{
    store.job = '程序员'
});