Pinia🍍 vue3/2 全新的全局状态管理库

关于 Pinia

可能你不太了解Pinia是啥,但是VueX肯定知道,他是vue全家桶成员之一,目前版本迭代到了VueX 4。根据当下的形势VueX 5是不会出来了,因为Vue官方的状态管理库已经更改为了 Pinia

Pinia可以理解为VueX 5,但是(Pinia舍弃了mutation和module两部分),既然官方把名字都改了,肯定是做了改动和升级,使用起来更加方便。

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

想要管理状态,首先要在项目中,创建一个数据仓库,将需要管理的状态,放在数据仓库中,一般创建的数据仓库名为store

安装 Pinia

安装命令:

ts 复制代码
yarn add pinia
// 或者
npm install pinia

安装完成后,我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程式。我们需要修改main.js,引入pinia提供的cteatePinia方法:

vue3的安装引入

ts 复制代码
// 基于vue3的安装引入 
import { createApp } from 'vue';
import { createPinia } from 'pinia';
//构建
const pinia = createPinia();
const app = createApp(App);
// 将Pinia挂载到Vue应用中
app.use(pinia).mount('#app');

vue2的安装引入

在Vue 2中使用,还需要安装组合 API:@vue/composition-api

js 复制代码
import Vue from 'vue'
import vueCompositionApi from '@vue/composition-api'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'
import App from './App.vue'

// piniaPersist是Pinia的辅助插件,要挂载到Pinia上,Pinia在挂载到Vue上
const pinia = createPinia()
pinia.use(piniaPersist)
// 注意下面的执行顺序,要先是组合式api,才可挂载Pinia
Vue.use(vueCompositionApi)
Vue.use(pinia)

new Vue({
  pinia,
  render: h => h(App),
}).$mount('#app')

store 数据仓库

store理解为一个公共组件,只不过该公共组件只存放数据,这些数据在其它所有的组件都能够访问或修改。

四个概念,stategettersactionspersist

  1. state用于存放数据的地方
  2. getters定义计算关系的地方
  3. actions定义方法的地方
  4. persist设置数据持久化的属性

1、使用pinia提供的defineStore()方法来创建一个store数据仓库

ts 复制代码
// src/store/formInfo.js
import { defineStore } from 'pinia';

// 第一个参数是应用程序中 store 的唯一 id(给创建的store一个名字,方便组件使用)
const useFormInfoStore = defineStore('formInfo', {
    // 数据
    state: () => {
        return {
            name:'hello word'
        }
    },
    // 计算
    getters: {

    },
    // 方法
    actions: {
        setToken (value: string) {
          this.name = value
        }
    },
    persist: {
        enabled: true,
        ......
    }
})

export default useFormInfoStore;

defineStore接收两个参数:

  • name:一个字符串,必传项,该store的唯一id。
  • options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。

可以根据项目情况定义任意数量的store存储不同功能模块的数据,一个store就是一个函数,它和Vue3的实现思想也是一致的。

2、使用store

可以在任意组件中引入定义的store来进行使用,在使用前要进行实例化

ts 复制代码
<script setup>
// 引入定义
import useFormInfoStore from '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
</script>

store 被实例化后,你就可以直接在 store 上访问 stategettersactions 中定义的任何属性。

3、解构使用store

store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构 ,如果我们想要提取store中的属性同时保持其响应式的话,我们需要使用Pinia中的storeToRefs()方法,它将为响应式属性创建refs。

ts 复制代码
<script setup>
import { storeToRefs } from 'pinia';
// 引入定义
import useFormInfoStore from '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();

const { name, age } = formInfoStore; // ❌ 此时解构出来的name和age不具有响应式

const { name, age } = storeToRefs(formInfoStore); // ✅ 此时解构出来的name和age是响应式引用
</script>

状态管理器持久化

状态管理器就是defineStore()创建的一个store仓库

不持久化带来的问题

在VueX中管理的状态都是静态的,只要页面刷新,使用到该状态值的地方,都会变回初始值。

pinia所管理的数据其实也相当于静态数据,它定义在前端代码中,在页面中某处展示时,如果用户修改获取到的state中的值并刷新页面,发现数据会变回到它最开始定义的状态。

如果想要实现,修改的值代替原来定义的值,并且刷页面也不会再回到定义的状态,就需要开启持久化操作,将数据保存到浏览器中。

持久化的实现方法

Pinia 配套插件 pinia-plugin-persist

安装pinia-plugin-persist

ts 复制代码
yarn add pinia-plugin-persist
// 或者
npm i pinia-plugin-persist

引入pinia-plugin-persist

ts 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'
// 挂载到 pinia 上
const pinia = createPinia()
pinia.use(piniaPersist)

createApp({})
  .use(pinia)
  .mount('#app')

在文件 tsconfig.ts 中配置

ts 复制代码
// tsconfig.ts
{
  "compilerOptions": {
    "types": [
      "pinia-plugin-persist"
    ]
  },
}

在创建store数据仓库时,可以配置 persist 属性,来进行控制存储的状态。

persist中有4个配置属性:

  1. enabled属性:为true时,对整个store中的state内容进行存储,默认存储方式为sessionStorage
  2. storage属性:指定存储方式,可以为localStorage或sessionStorage,默认为后者
  3. paths属性:对state 中的字段,按组进行打包储存
  4. key属性:自定义存储的 key,默认是 store.$id

基础用法

ts 复制代码
persist: { 
    enabled: true
},

进阶用法,除了 enabled 属性,都是进阶用法,并且在strategies数组配置

ts 复制代码
import { defineStore } from "pinia"

export const useStore = defineStore("YourStore", () => {
    const foo = ref("foo")
    const bar = ref("bar")
    return { foo, bar }
}, {
    persist: {
        enabled: true,
        strategies: [{
            // 自定义存储的 key,默认是 store.$id
            key: "custom storageKey",
            // 可以指定任何 extends Storage 的实例,默认是 sessionStorage
            storage: localStorage,
            // state 中的字段名,按组打包储存
            paths: ["foo", "bar"]
        }],
    }
})

举个例子:

ts 复制代码
export const useUserStore = defineStore('storeUser', {
  state () {
    return {
      firstName: 'S',
      lastName: 'L',
      accessToken: 'xxxxxxxxxxxxx',
    }
  },
  persist: {
    enabled: true,
    strategies: [
      { storage: sessionStorage, paths: ['firstName', 'lastName'] }, // firstName 和 lastName字段用sessionStorage存储
      { storage: localStorage, paths: ['accessToken'] }, // accessToken字段用 localstorage存储
    ],
  },
})

State

state为保存store数据库中数据的容器,defineStore传入的第二个参数options配置项里面,就包括state属性。

ts 复制代码
// src/store/formInfo.js
import { defineStore } from 'pinia';

const useFormInfoStore = defineStore('formInfo', {
   // 推荐使用 完整类型推断的箭头函数
   state: () => {
      return {
        // 所有这些属性都将自动推断其类型
        name: 'Hello World',
        age: 18,
        isStudent: false
      }
   }
  
   // 还有一种定义state的方式,不太常见,了解即可
   // state: () => ({
   //    name: 'Hello World',
   //    age: 18,
   //    isStudent: false
   // })
})

export default useFormInfoStore;

默认情况下,您可以通过 store 实例来直接读取和写入状态:

ts 复制代码
<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();

console.log(formInfoStore.name); // 'Hello World'
formInfoStore.age++;  // 19
</script>

操作state的方法

pinia还提供了方法,我们使用和操作state:$reset$patch$state$subscribe

  1. $reset 重置状态,将状态重置成为初始值
  2. $patch 支持对state对象的部分批量修改
  3. <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a t e 通过将其 state 通过将其 </math>state通过将其state 属性设置为新对象来替换 Store 的整个状态
  4. $subscribe 订阅store中的状态变化
ts 复制代码
<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();

console.log(formInfoStore.name); // 'Hello World'
// 直接修改state中的属性
formInfoStore.age++;  // 19

// 1.$reset 重置状态,将状态重置成为初始值
formInfoStore.$reset();
console.log(formInfoStore.age); // 18
  
// 2.$patch 支持对state对象的部分批量修改
formInfoStore.$patch({
    name: 'hello Vue',
    age: 198
});
  
// 3.$state 通过将其 $state 属性设置为新对象来替换 Store 的整个状态
formInfoStore.$state = {
  name: 'hello Vue3',
  age: 100,
  gender: '男'
}

// 4.$subscribe 订阅store中的状态变化
formInfoStore.$subscribe((mutation, state) => {
  // 监听回调处理
}, {
  detached: true  // 💡如果在组件的setup中进行订阅,当组件被卸载时,订阅会被删除,通过detached:true可以让订阅保留
})
</script>

注意几种操作方法的问题

  • 同直接修改state中的属性不同,通过$patch方法更新多个属性时,在devtools的timeline中,是合并到一个条目中的
  • $state在进行替换时,会更新已有的属性,新增原来state不存在的属性,针对不在替换范围内的,则保持不变。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> s u b s c r i b e 只会订阅到 p a t c h e s 引起的变化,即上面的通过 subscribe只会订阅到patches引起的变化,即上面的通过 </math>subscribe只会订阅到patches引起的变化,即上面的通过patch方法和$state覆盖时会触发到状态订阅,直接修改state的属性则不会触发。

Getters

pinia中的getters可以完全等同于Store状态的计算属性,通常在defineStore中的getters属性中定义。同时支持组合多个getter,此时通过this访问其他getter。

ts 复制代码
import { defineStore } from 'pinia';

const useFormInfoStore = defineStore('formInfo', {
    state: () => {
        return {
            name: 'Hello World',
            age: 18,
            isStudent: false,
            gender: '男'
        };
    },
    getters: {
        // 仅依赖state,通过箭头函数方式
        isMan: (state) => {
            return state.gender === '男';
        },
        isWoman() {
            // 通过this访问其他getter,此时不可以用箭头函数
            return !this.isMan;
        }
    }
});

export default useFormInfoStore;

在使用时,我们可以直接在store实例上面访问getter:

ts 复制代码
<template>
  <div>The person is Man: {{ formInfoStore.isMan }} or is Woman: {{ formInfoStore.isWoman }}</div>
</tempalte>

<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
</script>

getter进阶用法

通常getter是不支持额外传参的,但是我们可以从getter返回一个函数的方式来接收参数:

ts 复制代码
import { defineStore } from 'pinia';

const useFormInfoStore = defineStore('formInfo', {
    state: () => {
        return {
            name: 'Hello World',
            age: 18,
            isStudent: false,
            gender: '男'
        };
    },
    getters: {
        isLargeBySpecialAge: (state) => {
          return (age) => {
             return state.age > age
          }
        }
    }
});

export default useFormInfoStore;

在组件中使用时即可传入对应参数,注意,在这种方式时,getter不再具有缓存性

ts 复制代码
<template>
  <div>The person is larger than 18 years old? {{ formInfoStore.isLargeBySpecialAge(18) }}</div>
</tempalte>

<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
</script>

Actions

定义在defineStore中的actions属性内,常用于定义业务逻辑使用。action可以是异步的,可以在其中异步调用 任何 API 甚至其他操作。

ts 复制代码
import { defineStore } from 'pinia';

const useFormInfoStore = defineStore('formInfo', {
    state: () => {
        return {
            name: 'Hello World',
            age: 18,
            isStudent: false,
            gender: '男'
        };
    },
    getters: {
        isMan: (state) => {
            return state.gender === '男';
        },
        isWoman() {
            return !this.isMan;
        }
    },
    actions: {
        incrementAge() {
            this.age++;
        },
        async registerUser() {
            try {
                const res = await api.post({
                    name: this.name,
                    age: this.age,
                    isStudent: this.isStudent,
                    gender: this.gender
                });
                showTips('用户注册成功~');
            } catch (e) {
                showError('用户注册失败!');
            }
        }
    }
});

export default useFormInfoStore;

使用

ts 复制代码
<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
  
const registerUser = () => {
  formInfoStore.registerUser();
}
</script>

$onAction()方法

使用 store.$onAction() 订阅 action 及其结果。传递给它的回调在 action 之前执行。 after 处理 Promise 并允许您在 action 完成后执行函数,onError 允许您在处理中抛出错误。

ts 复制代码
const unsubscribe = formInfoStore.$onAction(
  ({
    name, // action 的名字
    store, // store 实例
    args, // 调用这个 action 的参数
    after, // 在这个 action 执行完毕之后,执行这个函数
    onError, // 在这个 action 抛出异常的时候,执行这个函数
  }) => {
    // 记录开始的时间变量
    const startTime = Date.now()
    // 这将在 `store` 上的操作执行之前触发
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // 如果 action 成功并且完全运行后,after 将触发。
    // 它将等待任何返回的 promise
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // 如果 action 抛出或返回 Promise.reject ,onError 将触发
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// 手动移除订阅
unsubscribe()

和$subscribe类似,在组件中使用时,组件卸载,订阅也会被删除,如果希望保留的话,需要传入true作为第二个参数。

ts 复制代码
<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();

formInfoStore.$onAction(callback, true);
</script>

纯composition api方法,应用Pinia

举个例子:

ts 复制代码
import { defineStore } from "pinia";
import { ref, computed } from 'vue'

export const useSettingStore = defineStore('setting', ()=> {
  const name = ref('大哥')
  const age = ref(18)

  const NameDescribe = computed(() => `她的名字为${name.value}`)

  function changeSettingAge() {
    age.value = age.value == 18 ? 20 : 18
  }

  function setName(e: string) {
    name.value = e
  }
  // 传递的函数要 return 返回出来才可以使用
  return { name, age, NameDescribe, changeSettingAge, setName }
})

// 使用
// home.vue
<el-button @click="changeSetting">{{
  settingStore.NameDescribe
}}</el-button>

import { useSettingStore } from "@/stores/setting";
const settingStore = useSettingStore();
const changeSetting = () => {
  settingStore.changeSettingAge();
  settingStore.setName("二弟");
};

// 另一种使用方法
<el-button @click="changeSetting">{{
  titleDescribe
}}</el-button>

import { storeToRefs } from "pinia";
import { useSettingStore } from "@/stores/setting";
const { name, age, NameDescribe } = storeToRefs(useSettingStore())
const { changeSettingAge, setName } = useSettingStore()
const changeSetting = () => {
  changeSettingAge();
  setNmae("二弟");
  console.log(name.value)
};

Pinia 与 Vuex 对比

VueX 的基本思想借鉴了Flux。Flux 是 Facebook 在构建大型 Web 应用程序时为了解决数据一致性问题而设计出的一种架构,它是一种描述状态管理的设计模式。绝大多数前端领域的状态管理工具都遵循这种架构,或者以它为参考原型。Vuex在它的基础上进行了一些设计上的优化:

Pinia舍弃了mutation和module两部分,这样我们在使用时就更加的便捷。

相关推荐
天下无贼!1 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr1 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林1 小时前
npm发布插件超级简单版
前端·npm·node.js
罔闻_spider1 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔1 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠2 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
小晗同学2 小时前
Vue 实现高级穿梭框 Transfer 封装
javascript·vue.js·elementui
盏灯2 小时前
前端开发,场景题:讲一下如何实现 ✍电子签名、🎨你画我猜?
前端
WeiShuai2 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
Wandra2 小时前
很全但是超级易懂的border-radius讲解,让你快速回忆和上手
前端