vue中状态管理Pina的使用
1.pinia是什么?
如果你学过Vue2,那么你一定使用过Vuex。我们都知道Vuex在Vue2中主要充当状态管理的角色,所谓状态管理,简单来说就是一个存储数据的地方,存放在Vuex中的数据在各个组件中都能访问到,它是Vue生态中重要的组成部分。
既然Vuex那么重要,那么在Vue3中岂能丢弃!
在Vue3中,可以使用传统的Vuex来实现状态管理,也可以使用最新的pinia来实现状态管理,我们来看看官网如何解释pinia的。微信搜索公众号:架构师指南,回复:架构师 领取资料 。
官网解释:
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。
从上面官网的解释不难看出,pinia和Vuex的作用是一样的,它也充当的是一个存储数据的作用,存储在pinia的数据允许我们在各个组件中使用。
实际上,pinia就是Vuex的升级版,官网也说过,为了尊重原作者,所以取名pinia,而没有取名Vuex,所以大家可以直接将pinia比作为Vue3的Vuex。
2.为什么要使用pinia?
很多小伙伴内心是抗拒学习新东西的,比如我们这里所说的pinia,很多小伙伴可能就会抛出一系列的疑问:为什么要学习pinia?pinia有什么优点吗?既然Vue3还能使用Vuex为什么我还要学它?…
针对上面一系列的问题,我相信很多刚开始学习pinia的小伙伴都会有,包括我自己当初也有这个疑问。当然,这些问题其实都有答案,我们不可能平白无故的而去学习一样东西吧!肯定它有自己的优点的,所以我们这里先给出pinia的优点,大家心里先有个大概,当你熟练使用它之后,在会过头来看这些优点,相信你能理解。
优点:
Vue2和Vue3都支持,这让我们同时使用Vue2和Vue3的小伙伴都能很快上手。
pinia中只有state、getter、action,抛弃了Vuex中的Mutation,Vuex中mutation一直都不太受小伙伴们的待见,pinia直接抛弃它了,这无疑减少了我们工作量。
pinia中action支持同步和异步,Vuex不支持
良好的Typescript支持,毕竟我们Vue3都推荐使用TS来编写,这个时候使用pinia就非常合适了
无需再创建各个模块嵌套了,Vuex中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响。
体积非常小,只有1KB左右。
pinia支持插件来扩展自身功能。
支持服务端渲染。
pinia的优点还有非常多,上面列出的主要是它的一些主要优点,更多细节的地方还需要大家在使用的时候慢慢体会。
3.准备工作
想要学习pinia,最好有Vue3 的基础,明白组合式API是什么。如果你还不会Vue3,建议先去学习Vue3。
本篇文章讲解pinia时,全部基于Vue3来讲解,至于Vue2中如何使用pinia,小伙伴们可以自行去pinia官网学习,毕竟Vue2中使用pinia的还是少数。
项目搭建:
我们这里搭建一个最新的Vue3 + TS + Vite项目。
执行命令:
npm create vite@latest my-vite-app --template vue-ts
讯享网
运行项目:
讯享网npm install npm run dev
删除app.vue中的其它无用代码,最终页面如下:

讯享网
4.pinia基础使用
4.1 安装pinia
和vue-router、vuex等一样,我们想要使用pinia都需要先安装它,安装它也比较简单。
安装命令:
yarn add pinia # 或者使用 npm npm install pinia
安装完成后我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程序,简单来说就是创建一个存储数据的数据桶,放到应用程序中去。
修改main.js,引入pinia提供的createPinia方法,创建根存储。
代码如下:
讯享网// main.ts import {
createApp } from "vue"; import App from "./App.vue"; import {
createPinia } from "pinia"; const pinia = createPinia(); const app = createApp(App); app.use(pinia); app.mount("#app");
4.2 创建store
store简单来说就是数据仓库的意思,我们数据都放在store里面。当然你也可以把它理解为一个公共组件,只不过该公共组件只存放数据,这些数据我们其它所有的组件都能够访问且可以修改。
我们需要使用pinia提供的defineStore()方法来创建一个store,该store用来存放我们需要全局使用的数据。
首先在项目src目录下新建store文件夹,用来存放我们创建的各种store,然后在该目录下新建user.ts文件,主要用来存放与user相关的store。
代码如下:
/src/store/user.ts import {
defineStore } from 'pinia' // 第一个参数是应用程序中 store 的唯一 id export const useUsersStore = defineStore('users', {
// 其它配置项 })
创建store很简单,调用pinia中的defineStore函数即可,该函数接收两个参数:
name:一个字符串,必传项,该store的唯一id。
options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
我们可以定义任意数量的store,因为我们其实一个store就是一个函数,这也是pinia的好处之一,让我们的代码扁平化了,这和Vue3的实现思想是一样的。
4.3 使用store
前面我们创建了一个store,说白了就是创建了一个方法,那么我们的目的肯定是使用它,假如我们要在App.vue里面使用它,该如何使用呢?
代码如下:
讯享网/src/App.vue <script setup lang="ts"> import {
useUsersStore } from "../src/store/user"; const store = useUsersStore(); console.log(store); </script>
使用store很简单,直接引入我们声明的useUsersStore 方法即可,我们可以先看一下执行该方法输出的是什么:

4.4 添加state
我们都知道store是用来存放公共数据的,那么数据具体存在在哪里呢?前面我们利用defineStore函数创建了一个store,该函数第二个参数是一个options配置项,我们需要存放的数据就放在options对象中的state属性内。
假设我们往store添加一些任务基本数据,修改user.ts代码。
代码如下:
export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂", age: 25, sex: "男", }; }, });
上段代码中我们给配置项添加了state属性,该属性就是用来存储数据的,我们往state中添加了3条数据。需要注意的是,state接收的是一个箭头函数返回的值,它不能直接接收一个对象。
4.5 操作state
我们往store存储数据的目的就是为了操作它,那么我们接下来就尝试操作state中的数据。

这个时候我们发现打印的结果里面多了几个属性,恰好就是我们添加的数据,修改App.vue,让这几个数据显示出来。
代码如下:
讯享网<template> <img alt="Vue logo" src="./assets/logo.png" /> <p>姓名:{
{
name }}</p> <p>年龄:{
{
age }}</p> <p>性别:{
{
sex }}</p> </template> <script setup lang="ts"> import {
ref } from "vue"; import {
useUsersStore } from "../src/store/user"; const store = useUsersStore(); const name = ref<string>(store.name); const age = ref<number>(store.age); const sex = ref<string>(store.sex); </script>
输出结果:

上段代码中我们直接通过store.age等方式获取到了store存储的值,但是大家有没有发现,这样比较繁琐,我们其实可以用解构的方式来获取值,使得代码更简洁一点。
解构代码如下:
import {
useUsersStore } from "../src/store/user"; const store = useUsersStore(); const {
name, age, sex } = store;
上段代码实现的效果与一个一个获取的效果一样,不过代码简洁了很多。
child.vue代码如下:
讯享网<template> <h1>我是child组件</h1> <p>姓名:{
{
name }}</p> <p>年龄:{
{
age }}</p> <p>性别:{
{
sex }}</p> </template> <script setup lang="ts"> import {
useUsersStore } from "../src/store/user"; const store = useUsersStore(); const {
name, age, sex } = store; </script>

这样我们就实现了多个组件同时使用store中的数据。
代码如下:
<template> <img alt="Vue logo" src="./assets/logo.png" /> <p>姓名:{
{
name }}</p> <p>年龄:{
{
age }}</p> <p>性别:{
{
sex }}</p> <button @click="changeName">更改姓名</button> </template> <script setup lang="ts"> import child from './child.vue'; import {
useUsersStore } from "../src/store/user"; const store = useUsersStore(); const {
name, age, sex } = store; const changeName = () => {
store.name = "张三"; console.log(store); }; </script>
上段代码新增了changeName 方法,改变了store中name的值,我们点击按钮,看看最终效果:

我们可以看到store中的name确实被修改了,但是页面上似乎没有变化,这说明我们的使用的name不是响应式的。
很多小伙伴可能会说那可以用监听函数啊,监听store变化,刷新页面…

其实,pinia提供了方法给我们,让我们获得的name等属性变为响应式的,我们重新修改代码。
app.vue和child.vue代码修改如下:
讯享网import {
storeToRefs } from 'pinia'; const store = useUsersStore(); const {
name, age, sex } = storeToRefs(store);
我们两个组件中获取state数据的方式都改为上段代码的形式,利用pinia的storeToRefs函数,将sstate中的数据变为了响应式的。
除此之外,我们也给child.vue也加上更改state数据的方法。
child.vue代码如下:
<template> <h1>我是child组件</h1> <p>姓名:{
{
name }}</p> <p>年龄:{
{
age }}</p> <p>性别:{
{
sex }}</p> <button @click="changeName">更改姓名</button> </template> <script setup lang="ts"> import {
useUsersStore } from "../src/store/user"; import {
storeToRefs } from 'pinia'; const store = useUsersStore(); const {
name, age, sex } = storeToRefs(store); const changeName = () => {
store.name = "小猪课堂"; }; </script>
这个时候我们再来尝试分别点击两个组件的按钮,实现效果如下:

当我们store中数据发生变化时,页面也更新了!
此时,我们直接调用store的$reset()方法即可,继续使用我们的例子,添加一个重置按钮。
代码如下:
讯享网<button @click="reset">重置store</button> // 重置store const reset = () => {
store.$reset(); };
当我们点击重置按钮时,store中的数据会变为初始状态,页面也会更新。
4.5.5 批量更改state数据
前面我们修改state的数据是都是一条一条修改的,比如store.name="张三"等等,如果我们一次性需要修改很多条数据的话,有更加简便的方法,使用store的$patch方法,修改app.vue代码,添加一个批量更改数据的方法。
代码如下:
<button @click="patchStore">批量修改数据</button> // 批量修改数据 const patchStore = () => {
store.$patch({
name: "张三", age: 100, sex: "女", }); };
有经验的小伙伴可能发现了,我们采用这种批量更改的方式似乎代价有一点大,假如我们state中有些字段无需更改,但是按照上段代码的写法,我们必须要将state中的所有字段例举出了。
为了解决该问题,pinia提供的$patch方法还可以接收一个回调函数,它的用法有点像我们的数组循环回调函数了。
示例代码如下:
讯享网store.$patch((state) => {
state.items.push({
name: 'shoes', quantity: 1 }) state.hasChanged = true })
上段代码中我们即批量更改了state的数据,又没有将所有的state字段列举出来。
示例代码:
store.$state = {
counter: 666, name: '张三' }
上段代码会将我们提前声明的state替换为新的对象,可能这种场景用得比较少,这里我就不展开说明了。
当然我们这里的getter就是处理state数据。
代码如下:
讯享网export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂", age: 25, sex: "男", }; }, getters: {
getAddAge: (state) => {
return state.age + 100; }, }, });
上段代码中我们在配置项参数中添加了getter属性,该属性对象中定义了一个getAddAge方法,该方法会默认接收一个state参数,也就是state对象,然后该方法返回的是一个新的数据。
代码如下:
<template> <p>新年龄:{
{
store.getAddAge }}</p> <button @click="patchStore">批量修改数据</button> </template> <script setup lang="ts"> import {
useUsersStore } from "../src/store/user"; const store = useUsersStore(); // 批量修改数据 const patchStore = () => {
store.$patch({
name: "张三", age: 100, sex: "女", }); }; </script>
上段代码中我们直接在标签上使用了store.gettAddAge方法,这样可以保证响应式,其实我们state中的name等属性也可以以此种方式直接在标签上使用,也可以保持响应式。
当我们点击批量修改数据按钮时,页面上的新年龄字段也会跟着变化。
其实很简单,我们可以直接在getter方法中调用this,this指向的便是store实例,所以理所当然的能够调用到其它getter。
示例代码如下:
讯享网export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂", age: 25, sex: "男", }; }, getters: {
getAddAge: (state) => {
return state.age + 100; }, getNameAndAge(): string {
return this.name + this.getAddAge; // 调用其它getter }, }, });
上段代码中我们又定义了一个名为getNameAndAge的getter函数,在函数内部直接使用了this来获取state数据以及调用其它getter函数。
细心的小伙伴可能会发现我们这里没有使用箭头函数的形式,这是因为我们在函数内部使用了this,箭头函数的this指向问题相信大家都知道吧!所以这里我们没有采用箭头函数的形式。
那么在组件中调用的形式没什么变化,代码如下:
<p>调用其它getter:{
{
store.getNameAndAge }}</p>
示例代码:
讯享网export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂", age: 25, sex: "男", }; }, getters: {
getAddAge: (state) => {
return (num: number) => state.age + num; }, getNameAndAge(): string {
return this.name + this.getAddAge; // 调用其它getter }, }, });
上段代码中我们getter函数getAddAge接收了一个参数num,这种写法其实有点闭包的概念在里面了,相当于我们整体返回了一个新的函数,并且将state传入了新的函数。
接下来我们在组件中使用,方式很简单,代码如下:
<p>新年龄:{
{
store.getAddAge(1100) }}</p>
那么,如果我们有业务代码的话,最好就是卸载actions属性里面,该属性就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。
actions属性值同样是一个对象,该对象里面也是存储的各种各样的方法,包括同步方法和异步方法。
代码如下:
讯享网export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂", age: 25, sex: "男", }; }, getters: {
getAddAge: (state) => {
return (num: number) => state.age + num; }, getNameAndAge(): string {
return this.name + this.getAddAge; // 调用其它getter }, }, actions: {
saveName(name: string) {
this.name = name; }, }, });
上段代码中我们定义了一个非常简单的actions方法,在实际场景中,该方法可以是任何逻辑,比如发送请求、存储token等等。大家把actions方法当作一个普通的方法即可,特殊之处在于该方法内部的this指向的是当前store。
代码如下:
const saveName = () => {
store.saveName("我是小猪"); };
我们点击按钮,直接调用store中的actions方法即可。
main.ts代码:
讯享网import {
createApp } from "vue"; import App from "./App.vue"; import {
createPinia } from "pinia"; const pinia = createPinia(); const app = createApp(App); app.use(pinia); app.mount("#app");
user.ts代码:
import {
defineStore } from "pinia"; // 第一个参数是应用程序中 store 的唯一 id export const useUsersStore = defineStore("users", {
state: () => {
return {
name: "小猪课堂", age: 25, sex: "男", }; }, getters: {
getAddAge: (state) => {
return (num: number) => state.age + num; }, getNameAndAge(): string {
return this.name + this.getAddAge; // 调用其它getter }, }, actions: {
saveName(name: string) {
this.name = name; }, }, });
App.vue代码:
讯享网<template> <img alt="Vue logo" src="./assets/logo.png" /> <p>姓名:{
{
name }}</p> <p>年龄:{
{
age }}</p> <p>性别:{
{
sex }}</p> <p>新年龄:{
{
store.getAddAge(1100) }}</p> <p>调用其它getter:{
{
store.getNameAndAge }}</p> <button @click="changeName">更改姓名</button> <button @click="reset">重置store</button> <button @click="patchStore">批量修改数据</button> <button @click="saveName">调用aciton</button> <!-- 子组件 --> <child></child> </template> <script setup lang="ts"> import child from "./child.vue"; import {
useUsersStore } from "../src/store/user"; import {
storeToRefs } from "pinia"; const store = useUsersStore(); const {
name, age, sex } = storeToRefs(store); const changeName = () => {
store.name = "张三"; console.log(store); }; // 重置store const reset = () => {
store.$reset(); }; // 批量修改数据 const patchStore = () => {
store.$patch({
name: "张三", age: 100, sex: "女", }); }; // 调用actions方法 const saveName = () => {
store.saveName("我是小猪"); }; </script>
child.vue代码:
<template> <h1>我是child组件</h1> <p>姓名:{
{
name }}</p> <p>年龄:{
{
age }}</p> <p>性别:{
{
sex }}</p> <button @click="changeName">更改姓名</button> </template> <script setup lang="ts"> import {
useUsersStore } from "../src/store/user"; import {
storeToRefs } from 'pinia'; const store = useUsersStore(); const {
name, age, sex } = storeToRefs(store); const changeName = () => {
store.name = "小猪课堂"; }; </script>
所以,大家理解这种组合式编程的思想更重要,pinia无非就是以下3个大点:
- state
- getters
- actions
当然,本篇文章只是讲解了基础使用部分,但是在实际工作中也能满足大部分需求了,如果还有兴趣学习pinia的其它特点,比如插件、订阅等等,可以移步官网:pinia官网 ( https://pinia.web3doc.top/ )。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/21686.html