vue全家桶状态管理之pinia

532 阅读4分钟

本篇我们简单介绍vue全家桶之一状态管理-pinia

pinia 是一个用于 Vue.js 的状态管理库,是 Vuex 的替代方案。Pinia 提供了更简单、更直观的 API,同时支持 Vue 3 的 Composition API。

在大型应用中,多个组件可能需要共享和同步同一份数据。例如,一个购物车应用中,多个组件可能需要访问和更新购物车的内容。我们如果使用传统的props进行传递参数太过繁琐,万一我们在进行曾祖父组件之间的传参那会使得整个项目难以维护与管理,这时pinia的出现很好的解决了中央式的数据状态管理情况

我们回到上述的例子,我们先来看看十分传统的父子传参,爷孙传参,与兄弟传参

父子

<!-- ParentComponent.vue -->
<template>
  <ChildComponent :message="parentMessage" />
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  data() {
    return {
      parentMessage: 'Hello from Parent'
    };
  }
};
</script>

<!-- ChildComponent.vue -->
<template>
  <p>{{ message }}</p>
</template>

<script>
export default {
  props: ['message']
};
</script>

爷孙

<!-- GrandparentComponent.vue -->
<template>
  <ParentComponent :message="grandparentMessage" @update="handleUpdate" />
</template>

<script>
import ParentComponent from './ParentComponent.vue';

export default {
  components: { ParentComponent },
  data() {
    return {
      grandparentMessage: 'Hello from Grandparent'
    };
  },
  methods: {
    handleUpdate(data) {
      console.log('Received from grandchild:', data);
    }
  }
};
</script>

<!-- ParentComponent.vue -->
<template>
  <ChildComponent :message="message" @update="$emit('update', $event)" />
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  props: ['message']
};
</script>

<!-- ChildComponent.vue -->
<template>
  <button @click="sendUpdate">Send Update</button>
</template>

<script>
export default {
  props: ['message'],
  methods: {
    sendUpdate() {
      this.$emit('update', 'Data from Grandchild');
    }
  }
};
</script>

上述例子的父子传参采用v-bind语法糖,在父组件导出相应的数据绑定到子组件标签,在子组件有一个代理对象数组接收父组件传递的参数,这样便完成了一个数据的父子组件通信,Props:父组件可以通过 props 向子组件传递数据。props 是子组件接收数据的接口,父组件在使用子组件时通过属性的方式传递数据。但如果涉及到大型项目,此方案一定行不通,大型项目数据业务的复杂程度超乎想象,这种简单的v-bind不能满足业务需求,我们请出今天的主角pinia

现在我们试想这样一种数据,如果这种数据能在所有组件响应式更新并且统一管理是不是能非常方便的解决这个问题,我们先不详细介绍pinia,我们直接上例子

有这样三个组件,compa,compb,compsuba,分别与app为父子,父子,爷孙,如果用传统的方法传递参数,则为上面的代码,但是我们用pinia的话我们只需要这样

首先创建在src目录下创建中央管理仓库store,里面创建你要管理的数据模块,这里我们命名为counter.js

image.png 我们随便拿一个组件来举例子

<template>
    <div>
        <h2>CompA</h2>
    <p>count:{{ count }}</p>
    <button @click="increment">增加</button>
    <CompSubA />
    </div>
</template>

<script setup>
//引入中央模块
import { useCounterStore } from '../store/counter';
import CompSubA from './CompSubA.vue';
//从中央模块解构
const CounterStore = useCounterStore();
const { count, increment } = CounterStore;
</script>

<style  scoped>

</style>

首先我们解构导入pinia模块,这里变成了userCounterStore对象,然后我们再从userCounterStore对象解构出我们想要的数据和函数,ok细心的大佬早就发现了,这里我们的代码有问题,这里两次解构模块导致我们的页面正常显示出数据的默认值,但是点击这鼠标后完全没反应,这说明我们的响应式对象失去响应式,这是很容易错的一个点,各位大佬一定要标价好

我们来看正确的代码

<template>
    <div>
        <h2>CompA</h2>
    <p>count:{{ CounterStorecount }}</p>
    <button @click="CounterStore.increment">增加</button>
    <CompSubA />
    </div>
</template>

<script setup>
//引入中央模块
import { useCounterStore } from '../store/counter';
import CompSubA from './CompSubA.vue';
//从中央模块解构
const CounterStore = useCounterStore();
const { count, increment } = CounterStore;
</script>

我们现在来看看屏幕效果

image.png

我们点击仓库内置的自增函数后发现数据依然是响应式

image.png

为了对比这种响应式数据消失响应式我们列出下列代码

<template>
    <div>
   CompSubA
   {{ count }}
   <hr>
   {{ counterStore.count}}
    </div>
</template>

<script setup>
import { useCounterStore } from '../store/counter';
const counterStore = useCounterStore();
const { count, increment } = counterStore;//只有值,响应式丢失
</script>

<style  scoped>

</style>

我们非常清楚的看到点击自增按钮后{{count}}的值没有发生变化

image.png

下面我们现学现用实现一个登录切换小图标的功能

image.png

要求点击登陆后实现登录的地方切换成中心仓库数据中的用户头像资源

我们来想想什么数据和函数要配置到仓库

import { defineStore } from "pinia";
import { ref,reactive } from "vue";
export const useUserStore=defineStore('user',()=>{
    const isLogin=ref(false);
    const toLogin=()=>{
        isLogin.value=true;
    }
    const toLogout=()=>{
        isLogin.value=false;
    }
    const userInfo=reactive({
        name:'',
        avatar:'',
        massage:0,
        uid:null
    })
    const setUserInfo=()=>{
        userInfo.name='张三'
        userInfo.avatar='https://p26-passport.byteacctimg.com/img/user-avatar/3d681bc963f73f6e7d62d165703441cb~140x140.awebp'
        userInfo.massage=10
        userInfo.uid=1234567890

    }
    return {isLogin,toLogin,toLogout,userInfo,setUserInfo}
})
   

没错,登录状态,登出函数,以及用户数据与用户设置函数

<template>
  <div>
    <div v-if="isLogin"> <img :src="userInfo.avatar" ></div>
  <div v-else> <button @click="login">登录</button></div>
  
  <CompA />
  <CompB />
  
  </div>
</template>

<script setup>

import CompA from './components/CompA.vue'
import CompB from './components/CompB.vue'
import { useUserStore } from './store/user';
import { toRefs } from 'vue';
const userStore = useUserStore();
const { isLogin,userInfo}=toRefs(userStore);
const { toLogin,toLogout,setUserInfo } = userStore;
const login=()=>{
  console.log('登录');
  toLogin();
  setUserInfo()
}
</script>

<style  scoped>

</style>

我们使用v-if判断是否渲染用户的头像数据,并且登录按钮绑定登录函数,实现资源切换与登录功能

这样我们点击登录后我们发现效果实现啦

image.png