1、为什么要使用Pinia?
Pinia 是 Vue 的存储库,它允许跨组件/页面共享状态
Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它取而代之的是新的建议,与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范。
官方文档:Pinia | The intuitive store for Vue.js
中文文档:Pinia 中文文档
2、项目中引入Pinia
在项目引入 pinia
有两种方案
- 在创建项目结构时选中
pinia
- 创建项目之后,手动下载安装
项目中安装
npm install pinia --save
创建store仓库
// 导入 Pinia 的 defineStore 方法,用于创建状态存储库
import { defineStore } from "pinia";// 使用 defineStore 创建一个名为 'countStore' 的状态存储库
// 第一个参数是 store 的唯一标识符(ID)
// 第二个参数是配置对象,包含状态、操作和获取器
export const useCountStore = defineStore("countStore", {// state 必须是一个函数,返回状态对象// 使用函数形式可以避免在服务端渲染时的交叉请求状态污染问题state: () => {return {count: 10 // 声明一个名为 count 的响应式状态,初始值为 10};}// 可以在此处继续添加 actions(操作)或 getters(获取器)// actions: { ... }, // 用于定义修改状态的方法// getters: { ... } // 用于定义基于状态的派生值
});
在main.js加载store
// 导入 Vue 的 createApp 方法,用于创建 Vue 应用实例
import { createApp } from 'vue'
// 导入根组件 App,这是 Vue 应用的入口组件
import App from './App.vue'
// 导入 Pinia 的 createPinia 方法,用于创建状态管理库实例
import { createPinia } from "pinia"/*** Vue 应用初始化流程:* 1. 创建 Vue 应用实例:createApp(App)* 2. 使用 Pinia 插件:.use(createPinia())* 这会为应用添加全局状态管理能力* 3. 挂载到 DOM:.mount('#app')* 将应用渲染到 index.html 中 id 为 "app" 的元素中* * 完整链式调用:* 创建应用 -> 集成状态管理 -> 挂载到页面*/
createApp(App).use(createPinia()) // 安装 Pinia 插件(必须挂载前调用).mount('#app') // 挂载应用,启动渲染/*
扩展说明:
- createPinia() 创建的是一个根存储库,所有组件都可以访问
- 挂载顺序必须为:先安装插件,再挂载应用
- 后续可以继续链式调用其他插件:.use(router).use(i18n)
*/
组件中使用
<template><!-- 显示 Pinia 状态管理中的 count 值 --><h3>Pinia</h3><p>{{store.count}}</p> <!-- 动态显示 store 中的 count 属性 -->
</template><script setup>
// 导入 Pinia store(状态管理模块),这里导入的是 useCountStore
import { useCountStore } from "@/store/count.js";// 使用 useCountStore 创建一个 store 实例
const store = useCountStore();
</script>
3、组合式API风格
如果你很喜欢组合式API的代码风格,我们甚至可以在Store中采用组合式API来实现,这会让你上手 pinia
更迅速。
创建新的仓库user
import { defineStore } from "pinia"; // 导入 Pinia 的 defineStore 函数
import { reactive, ref } from "vue"; // 导入 Vue 的 reactive 和 ref 函数// 定义一个 Pinia Store,使用箭头函数返回状态和操作
export const useUserStore = defineStore("userStore", () => {// 使用 reactive 创建一个响应式对象,表示 Store 的状态const user = reactive({name: "zs", // 用户的姓名,默认值为 "zs"age: 10 // 用户的年龄,默认值为 10});// 返回 Store 的状态和操作(如果有)。这里只返回了状态return {user // 将 user 对象暴露给外部使用};
});
页面获取仓库信息
<template><!-- 显示用户信息 --><h3>user</h3><!-- 动态显示 store 中 user 对象的 name 属性 --><p>{{store.user.name}}</p><!-- 动态显示 store 中 user 对象的 age 属性 --><p>{{store.user.age}}</p>
</template><script setup>
// 从 store 文件夹中的 user.js 文件中导入 useUserStore
import { useUserStore } from "@/store/user.js";// 调用 useUserStore 获取 Pinia Store 实例
const store = useUserStore();
</script>
4、核心概念-State
大多数时候,state 是 store 的核心部分。 我们通常从定义应用程序的状态开始。 在 Pinia 中,状态被定义为返回初始状态的函数。
// 导入 Pinia 的 defineStore 方法,用于创建状态存储库
import { defineStore } from "pinia";// 使用 defineStore 创建一个名为 'countStore' 的状态存储库
// 第一个参数是 store 的唯一标识符(ID)
// 第二个参数是配置对象,包含状态、操作和获取器
export const useCountStore = defineStore("countStore", {// state 必须是一个函数,返回状态对象// 使用函数形式可以避免在服务端渲染时的交叉请求状态污染问题state: () => {return {count: 10 // 声明一个名为 count 的响应式状态,初始值为 10};}// 可以在此处继续添加 actions(操作)或 getters(获取器)// actions: { ... }, // 用于定义修改状态的方法// getters: { ... } // 用于定义基于状态的派生值
});
使用组合式API获取状态信息
<template><!-- 显示 Pinia 状态管理中的 count 值 --><h3>Pinia</h3><p>{{store.count}}</p> <!-- 动态显示 store 中的 count 属性 -->
</template><script setup>
// 导入 Pinia store(状态管理模块),这里导入的是 useCountStore
import { useCountStore } from "@/store/count.js";// 使用 useCountStore 创建一个 store 实例
const store = useCountStore();
</script>
使用选项式API获取状态信息
<template><!-- 显示 Pinia Store 的状态 --><h3>Pinia</h3><!-- 动态显示 Store 中的 count 值 --><p>{{count}}</p><!-- 动态显示 Store 中的 count 值的两倍 --><p>{{doubleCount}}</p>
</template><script>
// 导入 Pinia 的 mapState 辅助函数
import { mapState } from "pinia";
// 导入定义的 Pinia Store
import { useCountStore } from "@/store/count.js";export default {// 定义计算属性(computed properties)computed: {// 使用 mapState 辅助函数将 Pinia Store 的状态映射到计算属性中...mapState(useCountStore, {// 直接映射 count 属性count: store => store.count,// 定义一个派生计算属性 doubleCount,基于 Store 的 count 属性doubleCount(store) {return store.count * 2; // 返回 count 的两倍}})}
};
</script>
5、核心概念-修改状态
在 pinia
中修改状态要比在 vuex
中修改状态简单的多,因为不用引入额外的概念,直接用 store.counter++
修改 store。
count.js
import {defineStore} from "pinia";
export const useCountStore = defineStore("countStore", {//state必须是函数类型state:()=>{return{count:10}}
})
组合式API修改状态
<template><h3>Pinia</h3><p>{{store.count}}</p><button @click="up">+1</button><button @click="down">-1</button>
</template>
<script setup>
import {useCountStore} from "@/store/count.js";
const store = useCountStore();
function up() {store.count = store.count + 1;
}
function down() {store.count = store.count - 1;
}
</script>
选项式API修改状态
<template><!-- 显示 Pinia Store 的 count 状态 --><h3>Pinia-选项式</h3><p>{{count}}</p><!-- 增加按钮,点击时调用 up 方法 --><button @click="up">+1</button><!-- 减少按钮,点击时调用 down 方法 --><button @click="down">-1</button>
</template><script>
// 导入 Pinia 的 mapState 和 mapWritableState 辅助函数
import { mapState, mapWritableState } from "pinia";
// 导入定义的 Pinia Store
import { useCountStore } from "@/store/count.js";export default {computed: {// 使用 mapState 辅助函数将 Pinia Store 的状态映射到计算属性中// 这里的 count 是只读的,不会直接改变 Store 的状态...mapState(useCountStore, {count: store => store.count, // 映射 count 属性为只读}),// 使用 mapWritableState 辅助函数将 Pinia Store 的状态映射到计算属性中// 这里的 count 是可写的,可以直接修改 Store 的状态...mapWritableState(useCountStore, ["count"]) // 映射 count 属性为可写},methods: {// 定义增加方法up() {this.count++; // 直接修改映射的 count 属性(通过 mapWritableState)},// 定义减少方法down() {this.count--; // 直接修改映射的 count 属性(通过 mapWritableState)}}
};
</script>
mapWritableState
的作用:
mapWritableState
也是 Pinia 提供的辅助函数,用于将 Store 中的状态映射到组件的计算属性中。与
mapState
不同的是,通过mapWritableState
映射的属性是可写的,可以直接修改 Store 中的状态。
6、核心概念-Getters
Getter 完全等同于 Store 状态的计算值。
store仓库添加getters
// 引入 Pinia 的 defineStore 函数,用于定义一个状态管理的 Store。
import { defineStore } from "pinia";// 使用 defineStore 定义一个名为 "count" 的 Store,这个名称在整个应用程序中是唯一的。
export const useCountStore = defineStore("count", {/*** 定义 Store 的状态(state)。* state 是一个函数,返回一个对象,包含所有的状态属性。*/state: () => {return {// 定义一个名为 "count" 的状态属性,初始值为 10。count: 10,};},/*** 定义 Getter,类似于 Vue 的计算属性(computed)。* Getter 可以对状态进行加工,返回新的值。*/getters: {// 定义一个名为 "getCount" 的 Getter。// 这个 Getter 会返回一个字符串,格式为 "当前Count:${state.count}"。getCount: (state) => "当前Count:" + state.count,},
});
组合式API中读取
<template><h3>Pinia</h3><p>{{ store.getCount }}</p>
</template>
<script setup>import { useCountStore } from "../store/count"
const store = useCountStore();
</script>
选项式API中读取
<template><h3>Pinia-选项式</h3><p>{{ getCount }}</p></template>
<script>
import { mapState } from "pinia"
import { useCountStore } from "../store/count"
export default {computed:{...mapState(useCountStore,["getCount"])}
}
</script>
7、核心概念-Actions
Actions 相当于组件中的 methods。 它们可以使用 defineStore()
中的 actions
属性定义,并且它们非常适合定义业务逻辑,主要是异步逻辑。
这里的action不是提交到mutation而是直接提交给state.
store添加actions
import { defineStore } from "pinia"
export const useCountStore = defineStore("count", {state: () => {return {count: 10}},getters: {getCount: (state) => "当前Count:" + state.count,doubleCount(state) {return this.getCount + state.count}},actions: {increment() {this.count++},decrement() {this.count--}}
})
组合式API中读取
<template><h3>Pinia-组合式</h3><p>{{store.getCount}}</p><button @click="add">添加</button><button @click="down">减少</button>
</template>
<script setup>
import {useCountStore} from "@/store/count.js";
const store = useCountStore();function add(){store.increaseCount();
}function down(){store.decreaseCount();
}</script>
选项式API中读取
<template><h3>Pinia-选项式</h3><p>{{ getCount }}</p><button @click="add">添加</button><button @click="down">减少</button></template>
<script>
import { mapState,mapActions } from "pinia"
import { useCountStore } from "../store/count"
export default {computed:{...mapState(useCountStore,["getCount"])},methods:{...mapActions(useCountStore,["increaseCount","decreaseCount"]),add(){this.increaseCount();},down(){this.decreaseCount();}}
}
</script>
Actions传递参数
actions:{increaseCount(num){this.count += num;},decreaseCount(num){this.count -= num;}}
组合式API
function add(){store.increaseCount(10);
}function down(){store.decreaseCount(10);
}
选项式API
methods:{...mapActions(useCountStore,["increaseCount","decreaseCount"]),add(){this.increaseCount(10);},down(){this.decreaseCount(10);}}
异步操作
安装axios
npm install --save axios
创建banner仓库
import {defineStore} from "pinia";
import axios from "axios";
export const useBannerStore = defineStore("banner",{state:()=>{return{banner:[]}},actions:{setBanner(url){axios.get(url).then(res=>{this.banner=res.data.banner;}).catch(error=>{console.log(error)})}}
})
组合式API显示页面
<template><h3>网络请求</h3><ul><li v-for="(item,index) in store.banner" :key="index"><h3>{{ item.title }}</h3><p>{{ item.content }}</p></li></ul>
</template>
<script setup>
import { onMounted } from "vue"
import { useBannerStore } from "../store/banner"
const store = useBannerStore();
onMounted(() =>{store.setBanner("http://iwenwiki.com/api/blueberrypai/getIndexBanner.php")
})
</script>
8、解构赋值响应式
如果直接从 pinia
中解构数据,会丢失响应式。
解构赋值如下:
const obj = {name: "zs",age:10
}const {age} = obj
const {name} = obj
<template><h3>Pinia</h3><p>{{ count }}</p><button @click="addCountHandler">增加</button><button @click="minCountHandler">减少</button>
</template>
<script setup>import { useCountStore } from "../store/count"
const store = useCountStore();
const { count } = store
function addCountHandler(){store.increment(5)
}
function minCountHandler(){store.decrement(5)
}
</script>
上述代码,我们操作加减的时候会发现,内容根本不会发生变化,这就是我们使用解构赋值之后,失去了响应式,我们可以用 storeToRefs
来解决找个问题。
<template><h3>Pinia-组合式</h3><p>{{ count }}</p><button @click="addCountHandler">增加</button><button @click="minCountHandler">减少</button>
</template>
<script setup>
import {storeToRefs} from "pinia"
import {useCountStore} from "../store/count"const store = useCountStore();
const {count} = storeToRefs(store)function addCountHandler() {store.increaseCount(5)
}function minCountHandler() {store.decreaseCount(5)
}
</script>
9、Pinia热更新
pinia
支持热模块替换,因此你可以编辑store,并直接在您的应用程序中与它们交互,而无需重新加载页面,允许您保持现有的状态,添加,甚至删除state,action和getter。
只需要在store仓库的对应的js文件添加:acceptHMRUpdate 相关配置即可,如下。
import { defineStore, acceptHMRUpdate } from "pinia"
import axios from "axios"export const useCountStore = defineStore("count", {state: () => {return {count: 10}},getters: {getCount: (state) => "当前Count:" + state.count}
})if (import.meta.hot) {import.meta.hot.accept(acceptHMRUpdate(useCountStore, import.meta.hot))
}
10、核心概念-插件
由于是底层 API,Pinia Store可以完全扩展,这一扩展就是插件
- 向 Store 添加新属性
- 定义 Store 时添加新选项
- 为 Store 添加新方法
- 包装现有方法
- 更改甚至取消操作
- 实现本地存储等副作用(主要是这个)
- 仅适用于特定 Store
创建仓库
// 定义仓库
import { defineStore } from "pinia"
export const useCountStore = defineStore("count",{state:() =>{return{count:10}}
})
定义插件
创建plugin文件,在该文件夹下面创建对应的插件
// 定义一个 Pinia 插件
export function piniaStoragePlugins({ store }) {// 输出当前 store 的 count 值,用于调试或验证插件是否正确加载console.log(store.count);// 使用 $subscribe 方法监听 store 的变化// 每当 store 中的属性发生改变时,回调函数会被触发store.$subscribe(() => {// 输出变化后的 count 值,用于实时监控状态的变化console.log(store.count);});
}
在main.js文件引入定义的插件
// 导入 Vue 的 createApp 函数,用于创建 Vue 应用实例
import { createApp } from 'vue';// 导入你的 Vue 根组件 App.vue
import App from './App.vue';// 导入 Pinia 的 createPinia 函数,用于创建一个 Pinia 实例
import { createPinia } from "pinia";// 导入一个 Pinia 插件(假设在 ./stores/plugins/count.js 文件中定义)
// 这个插件可能是用来扩展 Pinia 功能的
import { piniaStorePlugins } from "@/stores/plugins/count.js";// 创建一个 Pinia 实例
const pinia = createPinia();// 使用插件扩展 Pinia 实例
// 这里的 piniaStorePlugins 是一个 Pinia 插件,可以通过它为 Pinia 添加额外的功能
pinia.use(piniaStorePlugins);// 创建 Vue 应用实例
// 使用 .use(pinia) 将 Pinia 实例注入到 Vue 应用中
// 这样,所有的 Vue 组件都可以访问 Pinia 的状态管理功能
// 最后,将应用挂载到 DOM 中的 #app 元素上
createApp(App).use(pinia).mount('#app');
视图显示
// 视图操作
<template><h3>Pinia插件</h3><p>{{ store.count }}</p><button @click="addHandler">增加</button>
</template>
<script setup>
import { useCountStore } from "../stores/count"
const store = useCountStore()
function addHandler(){store.count++
}
</script>
11、核心概念-插件-本地存储
pinia
有个副作用,就是无法持久化,在浏览器刷新重置之后,会全部回复默认,这时候,我们可以利用插件实现本地持久化存储。
创建仓库
// 仓库
import { defineStore } from "pinia"
export const useCountStore = defineStore("count",{state:() =>{return{count:10}}
})
main.js引入仓库
和上面的一样,不写了。
创建持久化存储文件
// 定义一个函数,用于将键值对存储到 localStorage 中
export function setStorage(key, value) {// key: 存储的键名// value: 存储的值localStorage.setItem(key, value);// localStorage.setItem 是浏览器提供的 API,用于将指定的键值对存储到本地存储中// 注意:localStorage 只能存储字符串类型的值,如果要存储复杂的数据类型(如对象或数组),需要先将其转换为字符串(例如使用 JSON.stringify)。
}// 定义一个函数,用于从 localStorage 中读取指定键的值
export function getStorage(key) {// key: 要读取的键名return localStorage.getItem(key);// localStorage.getItem 是浏览器提供的 API,用于从本地存储中读取指定键的值// 注意:返回的值是字符串类型,如果原始数据是复杂的数据类型(如对象或数组),需要将其解析为对应的格式(例如使用 JSON.parse)。
}
通过插件实现本地存储
还要在main.js文件引入插件,见10、在main.js文件引入插件
// 定义插件
import { setStorage, getStorage } from "@/utils/storage.js";export function piniaStoragePlugins({ store }) {// 监听 store 的变化,当 store 状态发生改变时,触发回调函数store.$subscribe(() => {// 使用 setStorage 方法将 count 值存储到 localStoragesetStorage("count", store.count);});// 检查 localStorage 中是否保存了 count 值if (getStorage("count")) {// 如果存在,将 localStorage 中的 count 值赋给 store.countstore.count = getStorage("count");} else {// 如果不存在,将当前 store.count 值存储到 localStorage 中setStorage("count", store.count);}
}
视图显示
// 视图
<template><h3>Pinia插件</h3><p>{{ store.count }}</p><button @click="addHandler">增加</button>
</template>
<script setup>
import { useCountStore } from "../stores/count"
const store = useCountStore()
function addHandler(){store.count++
}
</script>
12、Pinia的数据持久化插件
好用的Pinia缺少持久化插件? pinia-plugin-persist
即可轻松解决!
安装插件
npm install --save pinia-plugin-persist
在main.js引入持久化插件
// 导入 Vue 的 createApp 函数,用于创建 Vue 应用实例
import { createApp } from 'vue';// 导入根组件 App.vue
import App from './App.vue';// 导入 Pinia 的 createPinia 函数,用于创建 Pinia 实例
import { createPinia } from "pinia";// 导入 pinia-plugin-persist 插件,用于实现 Pinia 状态的持久化
import piniaPersist from 'pinia-plugin-persist';// 创建 Pinia 实例
const pinia = createPinia();// 使用 pinia-plugin-persist 插件扩展 Pinia 实例
// 这会为 Pinia 提供状态持久化功能(例如将状态保存到 localStorage)
pinia.use(piniaPersist);// 创建 Vue 应用实例
// 使用 .use(pinia) 将 Pinia 实例注入到 Vue 应用中
// 最后,将应用挂载到 DOM 中的 #app 元素上
createApp(App).use(pinia).mount('#app');
在仓库中使用持久化插件
// 引入 Pinia 的 defineStore 函数,用于定义一个 Store
import { defineStore } from "pinia";// 定义一个名为 "count" 的 Store
export const useCountStore = defineStore("count", {// 定义 Store 的状态(state)state: () => {return {// 定义一个名为 "count" 的状态属性,初始值为 10count: 10};},// 配置持久化选项persist: {// 启用持久化功能enabled: true,// 定义持久化策略strategies: [{// 自定义存储的 key 值,用于在 localStorage 中标识存储的数据key: 'counts',// 指定存储方式,这里是使用浏览器的 localStoragestorage: localStorage,},],}
});