状态管理技术选型

目录

状态管理技术选型

1. Pinia概述

2. Vuex的应用

2.1 Vuex 的使用

2.2 测试组件

3. Pinia 的应用

3.1 Pinia 的使用

3.2 Pinia 的持久化

3.3 测试组件

4. Pinia 与 Vuex的比较总结


状态管理技术选型

1. Pinia概述

前端项目需要使用到状态管理框架,目前社区流行的有Vuex和Pinia两个框架。经过对比后,我们选择Pinia作为我们的状态管理库。下面将分点说明Pinia特点。

  • 背景

Pinia是根据Vue3而设计的,相比于Vuex来说历史包袱更少,它吸收了Vuex下个版本的很多核心概念以及项目开发经验而产生。

  • 框架量级

Pinia是轻量级的,体积很小。

  • 开发体验

Pinia有完整的 TypeScript 支持,与在 Vuex 中添加 TypeScript 相比,添加 TypeScript 更容易。

在使用上可以使用贴近Vuex的API风格来编写代码,也可以使用Vue3组合式API的风格来编写代码。

Store 的 action 被调度为常规的函数调用,而不是使用 dispatch 方法或 MapAction 辅助函数,这在 Vuex 中很常见。

  • 社区和生态系统

Pinia目前是Vue.js生态系统中增长最快的状态管理库之一,社区正在快速增长。然而Vuex毕竟是Vue.js核心团队推荐的状态管理库,拥有庞大的社区,核心团队成员做出了重大贡献。 Stack Overflow 上很容易找到 Vuex 错误的解决方案。

  • Pinia能力评估

接下来是评估Pinia是否能满足我们项目对于状态管理库的要求。

  1. 跨组件/页面数据共享(支持)

  2. 热更新(支持)

  3. 插件机制(支持)

  4. Devtools(支持)

  5. 数据持久化(支持,可通过插件机制实现)

2. Vuex的应用

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

2.1 Vuex 的使用

  • 安装Vuex

yarn add vuex@next --save
# 或者使用 npm
npm install vuex@next --save
  • 安装Vuex 后在main.ts入口文件注册Vuex

import { createApp } from 'vue';
import App from './App.vue';
import router from './router/index';
import store from './vuex/store'; // 引入store对象
​
const app = createApp(App);
app.use(router);
app.use(store); // 把store对象添加到vue实例上
app.mount('#app');
  • src文件夹下新建store文件夹,然后在store文件夹下新建store.ts文件

import { Store, createStore } from 'vuex';
// 配置vue+ts的项目里面使用vuex
declare module '@vue/runtime-core' {
  // declare your own store states
  interface State {
    count: number;
    list: string[];
    msg: string;
  }
​
  // provide typings for `this.$store`
  interface ComponentCustomProperties {
    $store: Store<State>;
  }
}
​
const store = createStore({
  // 数据
  state() {
    return {
      count: 1,
      list: ['马总', '刘总'],
      msg: '你好vue',
    };
  },
  // 方法 Mutations里面的函数必须是同步操作
  mutations: {
    // 方法的作用修改state中的变量,state是必须默认参数
    increment(state: any) {
      state.count++;
    },
    // 增加一个带参数的mutations方法
    setCount(state: any, num: number) {
      state.count = num;
    },
    setMsg(state: any, msg: string) {
      state.msg = msg;
    },
  },
  // 计算属性
  getters: {
    // 获取修饰后的name,第一个参数state为必要参数,必须写在形参上
    reverseMsg(state: any) {
      return state.msg.split('').reverse().join('');
    },
    num(state: any) {
      return state.count + 10;
    },
  },
  // 执行mutations里面的方法 异步操作放在actions
  actions: {
    // 默认第一个参数是context,其值是复制的一份store
    increment(context) {
      // 执行mutations里面的increment
      context.commit('increment');
    },
    // action就是去提交mutation的,什么异步操作都在action中消化了,最后再去提交mutation的
    // 直接将context结构掉,解构出commit,下面就可以直接调用了
    setMsg({ commit }, msg: string) {
      // 执行mutations里面的increment
      setTimeout(() => {
        commit('setMsg', msg);
      }, 1000);
    },
  },
});
​
// vuex的模块
// import storeModule1 from './storeModule1';
// import storeModule2 from './storeModule2';
​
// const store = createStore({
//   modules: {
//     storeModule1: storeModule1,
//     storeModule2: storeModule2,
//   },
// });
​
export default store;
 

2.2 测试组件

新建test.vue的测试组件,使用Vuex

<template>
  <div>
    vuex测试组件
    <br />
    <br />
    vuex--{{ count }}
    <br />
    <br />
    <button @click="increment">执行vuex里面的方法,改变count</button>
    <br />
    <br />
    <button @click="setCount">执行vuex里面的方法,进行传值</button>
    <br />
    <br />
    <hr />
    <ul>
      <li v-for="(item, index) in $store.state.list" :key="index">{{ item }}</li>
    </ul>
    <hr />
    <br />
    <br />
    获取getters的数据--{{ reverseMsg }}
    <br />
    <br />
    获取getters的数据--{{ num }}
    <br />
    <br />
    <button @click="setMsg">改变state里面的数据</button>
    <br />
    <br />
    <hr />
    触发actions里面的方法
    <br />
    <br />
    <button @click="$store.dispatch('increment')">actions increment</button>
    <br />
    <br />
    <button @click="setActionMsg">actions setMsg</button>
  </div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { mapGetters, mapState, useStore } from 'vuex';
// import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default defineComponent({
  // 组合式api写法
  setup() {
    const store = useStore();
​
    return {
      count: computed(() => {
        return store.state.count;
      }),
      increment: () => {
        // 触发vuex中mutation的方法
        store.commit('increment');
      },
      setCount: () => {
        // 触发vuex中mutation的方法 进行传值
        store.commit('setCount', 15);
      },
      reverseMsg: computed(() => {
        return store.getters.reverseMsg;
      }),
      num: computed(() => {
        return store.getters.num;
      }),
      setMsg: (): void => {
        store.commit('setMsg', '你好,我是mutations改变后的数据');
      },
      setActionMsg: () => {
        store.dispatch('setMsg', '你好,我是actions改变后的数据');
      },
    };
  },
​
  // 选项式api写法
  // computed: {
  //   // ...mapState(['count', 'list']),  // 经过解构后,自动添加到计算属性中,此时就可以直接像访问计算属性一样访问它
  //   // ...mapState({   // 赋别名接收对象,不是数组
  //   //   num: (state: any) => state.count,
  //   //   myList: (state: any) => state.list,
  //   // }),
​
  //   // ...mapGetters(['reverseMsg', 'num']),
  //   // ...mapState({
  //   //   reverseMsg: 'reverseMsg',
  //   //   num: 'num',
  //   // }),
​
  //   count(): number {
  //     // 使用vuex中state的数据
  //     return this.$store.state.count;
  //   },
​
  //   reverseMsg(): string {
  //     return this.$store.getters.reverseMsg;
  //   },
  //   num(): number {
  //     return this.$store.getters.num;
  //   },
  // },
  // methods: {
  // //...mapMutations(['increment']),
  // //...mapActions(['setMsg']),
  //   increment(): void {
  //     // 触发vuex中mutation的方法
  //     this.$store.commit('increment');
  //     this.increment();
  //   },
  //   setCount() {
  //     // 触发vuex中mutation的方法 进行传值
  //     this.$store.commit('setCount', 15);
  //   },
  //   setMsg() {
  //     this.$store.commit('setMsg', '你好我是改变后的数据');
  //   },
  //   setActionMsg(): void {
  //     this.$store.dispatch('setMsg', '你好我是改变后的数据');
  //   },
  // //  async setActionMsg(): void {
  // //     await this.setMsg('setMsg', '你好我是改变后的数据');
  // // },
  // },
});
</script>
<style lang="scss" scoped></style>

 

3. Pinia 的应用

Pinia 是 Vue 的状态管理库,它允许您跨组件/页面共享状态。

3.1 Pinia 的使用

  • 安装Pinia

yarn add pinia# 
#或者使用 npm
npm install pinia
  • 安装pinia后在main.ts入口文件注册pinia

import { createApp } from 'vue';
import App from './App.vue';
import router from './router/index';
import { createPinia } from 'pinia';
​
const app = createApp(App);
app.use(router);
const pinia = createPinia();
app.use(pinia);
app.mount('#app');
  • src文件夹下新建store文件夹,然后在store文件夹下新建mian.ts文件

import { defineStore } from 'pinia';
​
type User = {
  name: string;
  age?: number;
  sex?: string;
  msg?: string;
};
​
const login = (user: User): Promise<any> => {
  return new Promise(resolve => {
    setTimeout(() => {
      user.msg = '登陆成功';
      resolve(user);
    }, 2000);
  });
};
​
export const useMainStore = defineStore({
  id: 'mian',
  state: () => ({
    name: '超级管理员',
    count: 0,
    user: <User>{},
  }),
​
  // computed 计算属性
  getters: {
    nameLength: state => state.name.length,
    newName(): string {
      return `$-${this.name}--${this.nameLength}`;
    },
  },
​
  // methods 可以做同步、异步 提交state
  actions: {
    setCount(count: number) {
      this.count = count;
    },
    setUser(user: User) {
      this.user = user;
    },
    async setUserLogin(user: User) {
      const result = await login(user);
      this.user = result;
      this.setSex('女');
    },
    setSex(sex: string) {
      this.user.sex = sex;
    },
  },
});
​

3.2 Pinia 的持久化

  • 在store文件夹下封装一个Pinia 插件,文件名为piniaPlugin.ts

import { PiniaPluginContext } from 'pinia';
import { toRaw } from 'vue';
​
type Options = {
  key?: string;
};
​
const _piniaKey_ = 'pinia';
const setStorage = (key: string, value: any) => {
  localStorage.setItem(key, JSON.stringify(value));
};
const getStorage = (key: string) => {
  return localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key) as string) : {};
};
​
export const piniaPlugin = (options: Options) => {
  return (context: PiniaPluginContext) => {
    const { store } = context;
    const data = getStorage(`${options?.key ?? _piniaKey_}-${store.$id}`);
    store.$subscribe(() => {
      setStorage(`${options?.key ?? _piniaKey_}-${store.$id}`, toRaw(store.$state));
    });
    return {
      ...data,
    };
  };
};

  • 在main.ts入口文件注册Pinia 持久化插件

import { createApp } from 'vue';
import App from './App.vue';
import router from './router/index';
import { createPinia } from 'pinia';
import { piniaPlugin } from './store/piniaPlugin';
​
const app = createApp(App);
app.use(router);
const pinia = createPinia();
pinia.use(piniaPlugin({ key: 'pinia' }));
app.use(pinia);
app.mount('#app');

3.3 测试组件

新建test.vue的测试组件,使用Pinia

<template>
  <div>
    <p>state</p>
    用户名:{{ mainStore.name }}--- count:{{ mainStore.count }}
  </div>
  <hr />
  <div>
    <p>getters</p>
    nameLength:{{ mainStore.nameLength }}
    <br />
    newName:{{ mainStore.newName }}
  </div>
  <hr />
  <div>
    <p>修改store中的变量</p>
    <button @click="change1">第一种方式</button>
    <button @click="change2">第二种方式</button>
    <button @click="change3">第三种方式</button>
    <button @click="change4">第四种方式</button>
    <button @click="change5">第五种方式</button>
  </div>
  <hr />
  <div>
    <p>pinia解构不具有响应性解决办法</p>
    用户名:{{ name }}--- count:{{ count }}
  </div>
  <hr />
  <div>
    <p>actions</p>
    用户名:{{ mainStore.user }}
    <button @click="changeUser">同步 --- 修改用户</button>
    <button @click="userLogin">异步 --- 用户登陆</button>
  </div>
  <hr />
  <div>
    <p>reset</p>
    <button @click="reset">reset --- 恢复state初始值</button>
  </div>
  <hr />
</template>
​
<script setup lang="ts">
import { useMainStore } from '@/store/mian';
import { storeToRefs } from 'pinia';
​
const mainStore = useMainStore();
​
// 修改store中的变量
const change1 = () => {
  mainStore.count++;
};
const change2 = () => {
  mainStore.$patch({
    name: '修改后名称变化,nameLength也改变了',
    count: 2,
  });
};
const change3 = () => {
  mainStore.$patch(state => {
    state.name = '修改后名称变化,nameLength也改变了';
    state.count = 3;
  });
};
const change4 = () => {
  mainStore.$state = {
    name: '修改后名称变化,nameLength也改变了',
    count: 4,
    user: { name: '李四' },
  };
};
const change5 = () => {
  mainStore.setCount(5);
};
​
// pinia解构不具有响应性解决办法
// const { name, count } = mainStore;
const { name, count } = storeToRefs(mainStore);
​
// actions
const changeUser = () => {
  mainStore.setUser({ name: '张三' });
};
const userLogin = () => {
  mainStore.setUserLogin({ name: '王五', age: 40 });
};
​
// reset
const reset = () => {
  mainStore.$reset();
};
​
// state的值变化触发subscribe
mainStore.$subscribe(
  (args, state) => {
    console.log('========', args);
    console.log('========', state);
  },
  { detached: true } // 组件销毁仍然进行监听,还有deep,flush属性
);
​
// actions变化触发onAction
mainStore.$onAction(args => {
  args.after(() => {
    console.log('========', 'after');
  });
  console.log('========', args);
}, true); // 组件销毁仍然进行监听
</script>
​
<style lang="scss" scoped>
button {
  margin: 10px;
}
</style>

4. Pinia 与 Vuex的比较总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值