1. 为什么需要piana
vuex的问题
-
TS支持度不好
-
架构过于繁琐
state -> mutation -> action getters- state -> action(同步 + 异步) - getters
- 天然支持模块化 modules
替代方案 - piana
官方文档Pinia
git 地址 github.com/vuejs/pinia
2.介绍
-
Vue2 和Vue3 都支持
- 除了初始化安装和SSR配置之外,两者的API 都是相同
- 官方文档中主要针对vue3进行说明,必要的时候会提供vue2 的注释
-
支持Vue DevTools
- 跟踪 actions mutatlons 的时间线
- 在使用容器的组件就可以观察到容器本身
- 支持 time travel 更容易的调试功能
- 在vue2 中Pinla 使用的是Vuex现有接口,所以不能与Vuex一起使用
- 但是针对 Vue3 中调试工具支持还不够完美,比如还没有,time-travel 调试功能
-
模块热更新
- 无需重新加载页面即可修改你的容器
- 热更新的时候保持任何现有状态
-
支持插件扩展 Pinia 功能
-
相比 Vuex 有更好完美的TypeSrcipt 支持
-
支持服务器渲染
3.Vuex与pinia的区别
pinia 就是更好的vuex ,推荐在项目中直接使用它,尤其是使用了TypeScript 的项目
-
Vuex 中有四个核心概念
StateGettersMutationsActions -
Pinia 中有三个核心概念
StateGettesMutatlonsActions-
没有
mutations. mutations 被认为是非常冗长的,最初带来了devtoods集成,但这不再是问题 -
不再 有模块的嵌套结构,仍然可以通过在另一个store中导入和使用store来隐式嵌套store
但 Pinia 通过设计提供扁平结构,同时仍然支持 store 之间的交叉组合方式。你甚至可以拥有store 的循环依赖关系
-
更好支持 typeScript ,无需创建自定义的复杂包装器来支持,TypeScipt ,所有的内容都类型化,并且所有的Api的设计方式尽可能地利用TS类型判断
-
不再需要注入,导入函数 ,调用它们享受自动补全
-
无需动态添加 stores,可随手使用store来注册,因为是自动的,所以不用担心
-
没有命名空间模块,鉴于store的扁平架构,'命名空间’ store是其定义方式所固有的,可以说所有stores都是命名空间的
-
4. 安装和配置
1)安装pinia插件
提示:如果应用程序使用Vue2,需要安装组合式api包
@vue/composition-api
yarn add pinia
npm install pinia
2)注册pinia插件 - main.js
import { createPinia } from 'pinia'
app.use(createPinia()) //挂载到 Vue 根实例
5.使用
5.1创建store
store/counter.js
state
- 创建store,命名规则: useXxxxStore
import { defineStore } from 'pinia'
// 参数1:容器ID 必须唯一,将来pinia 会把所有的容器挂载到根容器
// 参数2:对象,可以提供state actions getters
// 返回值:一个函数,调用得到容器实例
const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0,
}
},
getters: {},
actions: {},
})
export default useCounterStore
getters
类似于组件computed,用来封装计算属性,有缓存的功能
- 箭头函数不能使用this. 修改值时
请使用 state - 类似于computed 数据修饰并且有缓存
getters:{
newCurrent ():number | string {
return ++this.current + this.newName
},
newName ():string {
return `$-${this.name}`
},
//使用箭头函数时,使用state修改值
newPrice:(state)=> `$${state.user.price}`
}
actions
- 在pinia中没有mutations,只有actions
- 不管是同步还是异步的代码,都在actions中完成
注意:不能使用箭头函数定义 actions,因为箭头函数绑定外部的this
import { defineStore } from 'pinia'
const useCounterStore = defineStore('counter', {
state: () => {
// 定义响应式数据
return {
count: 1,
foo:100,
arr:[1,2,3]
}
},
//类似计算属性,具有缓存功能
getters:{
// 函数接受一个可选参数state状态对象
counl10(state){
return state.count +10
},
//如果getters中使用了 this则必须手动指定返回值的类型,否则类型推导不出3
coun11():number{
return this.count +10
}
},
//注意:不能使用箭头函数定义 action,因为箭头函数绑定外部的this
actions: {
//对应$patch的数据修改--方法四
changeSltate(num:number){
this.count+=num
this.foo='hello'
this.arr.push(4)
//this.$path({})
// this.$path(state=>{})
}
}
}
})
export default useCounterStore
5.2 组件使用 app.vue
storeToRefs (响应式)
解构失去响应式情况,可用pinia 的storeToRefs
- 使用storeToRefs可以保证解构出来的
- storeToRefs:只能解构数据,不能解构函数
<script setup lang='ts'>
import {storeToRefs} from "pinia"
import useCounterStore from './store/counter'
const counterStore = useCounterStore() // count increment
// 解构会失去响应式
//const {count,foo}=counterStore
//解决办法就是使用 storeToRefs
// 把解构出来的数据做 ref 响应代理
const {count,foo}= storeToRefs(counterStore)
console.log(count.value)
</script>
<template>
<!-- 使用store中的响应式数据 -->
<p>{{ count }} </p>
<p> {{ foo }} </p>
</template>
$patch (批量修改)
批量处理 要修改的数据
<script setup lang='ts'>
import {storeToRefs} from "pinia"
import useCounterStore from './store/counter'
const counterStore = useCounterStore() // count increment
const {count,foo}= storeToRefs(counterStore)
//修改数据
const handleChangeStae=()=>{
// 方式一 最简单的方式
counterStore.count++
counterStore.foo='hello'
//方式二:如果需要修改多个数据,建议使用$patch 批量更新
counterStore.$patch({
count: counterStore.count++,
foo:'hello',
arr:[...counterStore.arr,4]
})
// 方式三,$patch 一个函数
counterStore.$patch(state=>{
state.count++
state.foo="hello"
state.arr.push(4)
})
// 方式四 逻辑比较多的时候可以封闭到 actions (推荐使用)
counterStore.changeState(10)
}
</script>
<template>
<!-- 使用store中的响应式数据 -->
<p>{{ count }} </p>
<p> {{ foo }} </p>
<p> {{ counterStore.arr }} </p>
</template>
$reset(重置)
重置
store至它的初始值
state: () => ({
user: <Result>{},
name: "default",
current:1
})
<script setup lang='ts'>
import useCounterStore from './store/counter'
const counterStore = useCounterStore() // count increment
const reset=()=>{
counterStore.$reset()//重置state数据
}
</script>
<template>
<!-- 使用store中的响应式数据 -->
<button @click='reset'></button>
<p>{{ count }} </p>
<p> {{ foo }} </p>
</template>
$subscribe(监听state)
监听 state值的变化则调用 $subscribe
- 如果你的组件卸载之后还想继续调用请设置第二个参数
detached
Test.$subscribe((args,state)=>{
//args==>object包含新值与旧值
// state==>实例
console.log(args,state);
},{
detached:true
})
$onAction(监听)
只要有actions被调用就会走这个函数
- 如果你的组件卸载之后还想继续调用请设置第二个参数 true
Test.$onAction((args)=>{
console.log(args);
},true)
5.3 pinia的模块化
- 在复杂项目中,不可能把多个模块的数据都定义到一个store中
- 一般来说会一个模块对应一个store
- 最后通过一个根store进行整合
-
新建
store/user.js文件// 模块一:存放数量 import { defineStore } from 'pinia' const useCounterStore = defineStore('couter', { state: () => { return { count: 11, money: 99999999 } } }) export default useCounterStore -
新建
store/couter.jsimport { defineStore } from 'pinia' // 参数1:容器ID 必须唯一,将来pinia 会把所有的容器挂载到根容器 // 参数2:对象,可以提供state actions getters // 返回值:一个函数,调用得到容器实例 const useCounterStore = defineStore('counter', { state: () => { return { count: 0, } }, getters: {}, actions: {}, }) export default useCounterStore -
新建
store/index.js// 统一管理所有的 pinia 模板 import useCounterStore from "./couter"; import useUserStore from "./user"; // 暴露一个函数 export default function useStore() { return { // 管理两个模块 counter: useCounterStore(), user: useUserStore() } } -
在组件中使用
<script setup> import useStore from './store/index.js' // 使用 counter, user 中的数据 const { counter, user } = useStore() </script> <template> <h1>pinia 中的模块化</h1> <h3>使用 counter 中的数据</h3> <div>counter.count:{{ counter.count }}</div> <div>counter.money:{{ counter.money }}</div> <h3>使用 user 中的数据 </h3> <div>user.name: {{ user.name }}</div> <div>user.age: {{ user.age }}</div> </template>
6持久化 pinia插件
创建 store/piniaMain.ts
import type {PiniaPluginContext } from 'pinia'
import { toRaw } from 'vue'
const __piniaKey = '__PINIAKEY__'
type OptPinia={
key?:string
paths?:string[]
}
//取值
const getStorage = (key: string) => {
return (localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key) as string) : {})
}
//存储
const setStorage = (key: string, value: any): void => {
localStorage.setItem(key, JSON.stringify(value))
}
const piniaPlugin = (options: OptPinia)=>{
return (context: PiniaPluginContext)=>{
const { store } = context;
const data = getStorage(`${options?.key ?? __piniaKey}-${store.$id}`)
store.$subscribe((args, state)=>{
let arrPaths = options.paths
if (!arrPaths?.includes(args.storeId))return
setStorage(`${options?.key ?? __piniaKey}-${store.$id}`, toRaw(store.$state))
},{
detached:true
})
return{
...store.$state,
...data
}
}
}
export default piniaPlugin
创建store/piniaStore.ts
import piniaPlugin from "@/store/piniaMain"
import { createPinia } from 'pinia'
const store=createPinia()
export default store.use(piniaPlugin({
key: "pinia", //存储的前缀key
paths: ['user', 'cart']//存储state中的那些数据
}))
在main.ts 注册
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPlugin from "@/store/piniaStore"
app.use(store)
const app = createApp(App)