Pinia 是 Vue 的存储库,Pinia和Vuex一样都是是vue的全局状态管理器,它允许跨组件/页面共享状态。实际上,其实Pinia就是Vuex5,官网也说过,为了尊重原作者,所以取名 pinia,而没有取名 Vuex,所以大家可以直接将 pinia 比作为 Vue3 的 Vuex。
创建一个被应用所使用的 Pinia 实例。返回:Pinia
StoreDefinition。创建一个检索 store 实例的 useStore 函数。
用 store 的所有state、getters和 插件添加的(plugin-added)state、 属性(properties)创建一个引用对象。类似于toRefs(),但专门为 Pinia store 设计
通过生成要在组件的 computed 字段中展开的对象,允许使用没有组合式API (setup())的存储。它接受 store 定义的列表。
export default {
computed: {
// 其它计算属性
...mapStores(useUserStore, useCartStore)
},
created() {
this.userStore // id为"user"的存储
this.cartStore // id为"cart"的存储
}
}
将 state 属性映射为只读的计算属性,虽然它可以通过这个访问组件实例,但它不会识别类型。
import { mapState } from 'pinia'
import { useCounterStore } from './store/counter.js'
const counterComputed = computed(()=>{
// 可以访问组件中的 this.count
// 与从 store.count 中读取的数据相同
...mapState(useCounterStore, ['count'])
// 与上述相同,但将其注册为 this.myOwnName
...mapState(useCounterStore, {
myOwnName: 'count',
double: store => store.count * 2,
magicVlue(store) {
return store.soneGetter + this.count + this.double
}
})
})
可修改 state 中的属性,但是不能像 mapState() 哪样传递一个函数。
import { mapWritableState } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
computed: {
// 可以访问组件中的 this.count,并允许设置它。
// this.count++
// 与从 store.count 中读取的数据相同
...mapWritableState(useCounterStore, ['count'])
// 与上述相同,但将其注册为 this.myOwnName
...mapWritableState(useCounterStore, {
myOwnName: 'count',
}),
},
}
通过生成要在组件的 methods 字段中铺开的对象,允许直接使用存储(store)中的操作(action),而不使用合成API (setup())。对象的值是操作(action),而键是结果方法的名称。
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
methods: {
// 访问组件内的 this.increment()
// 与从 store.increment() 调用相同
...mapActions(useCounterStore, ['increment'])
// 与上述相同,但将其注册为this.myOwnName()
...mapActions(useCounterStore, { myOwnName: 'increment' }),
},
}
yarn add pinia
# 或者使用 npm
npm install pinia
在mian.js中,创建根存储
import { createPinia } from 'pinia'
app.use(createPinia())
store实例相当于一个容器,里面存放的有类似于data,计算属性,方法之类的东西。通过defineStore()方法定义
在src下面创建一个store文件夹,再创建与之对应的js文件,比如user.js
defineStore:
与 Vue 的选项式 API 类似,我们也可以传入一个带有 state、actions 与 getters 属性的 Option 对象
option :
import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useUser = defineStore('user', {
// other options...
})
setup 函数:
与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂。
export const useCounterStore = defineStore('count',()=>{
const count = ref(0)
function increment(){
count.value++
}
return { count, increment }
})
注:store 是一个用 reactive 包装的对象,这意味着不需要再 getters 后面下 .value ,就像 setup 中的 props 一样,不允许解构。
<script setup>
const store = useCounterStore()
// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
const { name, doubleCount } = store
name // 将始终是 "Eduardo"
doubleCount // 将始终是 0
setTimeout(() => {
store.increment()
}, 1000)
// ✅ 这样写是响应式的
// 💡 当然你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>
<script lang="ts" setup>
import { useUser } from "../stroe/user";
const stroe = useUser();
console.log(stroe, "stroe");
</script>
import { defineStore } from 'pinia'
// 第一个参数是应用程序中 store 的唯一 id
// 第二个参数是配置对象
export const useUser = defineStore('user', {
// state是一个函数,返回一个对象
state: () => {
return {
name: "anna",
age: 16,
};
}
})
为了从 store 中提取属性时保持其响应性,需要使用 storeToRefs()。它将为每一个响应式属性创建引用,当只使用 store 得状态而不调用任何 action 时,非常有用。可以直接从 store 中解构 action ,因为他们也被绑定到 store 中。
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// name 和 age 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式(不是 ref 或 reactive )得属性
const { name, age } = storeToRefs(store)
// 作为 action 得 increment 可以直接解构
const { increment } = store
state 是 store 得核心。state 被定义为一个返回初始状态的函数,声明 state 建议使用箭头函数,会自动判断属性的类型。
typescript 写法:
const useStore = defineStore('user',{
state: ()=>({
userList: [] as UserInfo[],
user: null as UserInfo | null
})
})
interface UserInfo{
name: string,
age: number
}
定义节后 state,并添加 state() 得返回值的类型
interface State{
userList: UserInfo[],
user: UserInfo | null
}
interface UserInfo{
name: string,
age: number
}
const useStore = defineStore('user',{
state: State => {
return {
userList: [],
user: null
}
}
})
可以简单理解为一个配置对象,里边是对容器仓库的配置说明。当然这种说明是以对象的形式。默认情况下,可以直接访问 store 得实例 state属性,并对齐进行读写。
import { storeToRefs } from 'pinia'
const userStore = useUser()
// 如果直接解构出数据,这个数据不是响应式的。如果想要变成响应式的,需要调用storeToRefs方法
const { name, age } = storeToRefs(userStore)
userStore.name = 'bob'
除了 store.xxx 直接修改 store,还可以使用 $patch 方法,允许用一个 state 得补丁对象在同一时间更改多个属性。
// 可以用来修改单个属性
userStore.$patch({
name: 'lily'
})
// 这种回调函数的形式适合修改集合类的数据,比如数组
userStore.$patch((state) => {
state.age = 18
})
两种变更 store 方法的主要区别是,$patch() 允许你将多个变更归入 devtools 的同一个条目中。同时请注意,直接修改 state,$patch() 也会出现在 devtools 中,而且可以进行 time travel (在 Vue 3 中还没有)。
事实上开发过程中为了满足工程扁平结构化规范,一般通过Action结合实现
// 重置
userStore.$reset()
export const userCounterStore = defineStore('counter',()=>{
const count = ref(0)
function $reset(){
count.value++
}
return { count, $reset }
})
// 替换
userStore.$state = {
userName: 'tom',
age: 11
}
类似于 vuex 得 subscribe,可以通过 store 得 $subscribe() 方法侦听 state 及其变化,比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次。
cartStore.$subscribe((mutation, state) => {
// import { MutationTypr } from 'pinia'
mutation.type // 'dorect' | 'patch object' | 'patch function'
// 和 carStore.$id 一样
mutation.storeId // 'cart'
// 只有 mutation.type === 'patch onject'得情况下才可以用
mutation.payload // 传递给 cartStore.$patch() 得补丁对象
// 每当状态发生变化时,将整个 state 持久化到本地存储
localStorage.setItem('cart', JSON.stringify(state))
})
默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离:
const someStore = useSomeStore()
// 此订阅器几遍在组件卸载之后扔会被保留
someStore.$subscribe(callback, { detached: true })
setup store:
watch(pinia.state, state => {
loaclStorage.setItem('piniaState',JSON.stringify(state))
})
类似计算属性。用来监视或者说是计算状态的变化的,有缓存的功能。
推荐里面传递一个形参的箭头函数写法,不容易出错。如果使用非箭头函数,可以在函数中使用 this,但是在 getters 中使用形参 state 访问属性 typescript 无法识别。
getters: {
isAdult: (state) => {
return state.age >= 18 ? "成年人" : "未成年";
},
isAdult1(){
return this.age >= 18 ? "成年人" : "未成年";
}
},
// 直接获取
<div>{{userStore.isAdult}}</div>
getters: {
isAdult: (state) => {
return state.age >= 18 ? '成年人' : '未成年'
},
msg: (state) => {
// msg这个getter访问了自身的getter(isAdult)
return state.userName + state.isAdult
},
msg1(){
return this.userName + this.isAdult
}
}
Getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,你可以从 getter 返回一个函数,该函数可以接受任意参数:
getters: {
isAdmin: (state) => {
// 如果getter里面是返回的函数,那么它就可以传参数了
return (name) => name === 'admin' ? '是管理员' : '不是管理员'
}
}
// 直接获取
<div>{{userStore.isAdmin}}</div>
商品模块 good.js
import { defineStore } from 'pinia'
// 第一个参数是应用程序中 store 的唯一 id
// 第二个参数是配置对象
export const useGoods = defineStore('goods', {
// state是一个函数,返回一个对象
state: () => {
return {
goodsName: 'iphone'
}
},
getters: {
newIphone: (state) => {
return state.goodsName + ' 14 pro'
}
}
})
想在 user 模块中的 getters 中去获取 goods 模块的 newIphone
import { useGoods } from './goods.js'
// ..........
getters: {
info: (state) => {
// 获取goods模块的store
const goodsStore = useGoods()
return state.userName + '买了' + goodsStore.newIphone
}
}
类似于 getters,actions 也可以通过 this 访问整个 store 实例。action 是可以异步的,可以在他们里面 await 调用任何 api,以及其他 action。注意action中需要定义普通函数,这样才有自己的this能取到state属性
actions: {
// 这里的方法要写成普通函数,因为里面需要通过this去访问state里面的数据
changeNameAsync (newName) {
setTimeout(() => {
// actions里面可以访问state
this.userName = newName
}, 1000)
},
async getList(params) {
try {
this.list = await api.post(params)
} catch (err) {
console.log('err:' + err.msg)
}
}
}
Action 可以像函数或者通常意义上的方法一样被调用:
<templete>
<button @click='store.randomizeCounter()'/>
</templete>
<script setup>
const store = useCounterStore()
store.randomizeCounter()
</script>
import { defineStore } from 'pinia'
import { useAuthStore } from './auth-store'
export const useSettingStore = defineStore('setting', {
state: () => ({
preference: null
}),
actions:{
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preference = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
}
}
})
你可以通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。同样地,onError 允许你在 action 抛出错误或 reject 时执行一个回调函数。这些函数对于追踪运行时错误非常有用,类似于Vue docs 中的这个提示。
const unsubscribe = someStore.$onAction(
({
name, // action 名称
store, // store 实例,类似 `someStore`
args, // 传递给 action 的参数数组
after, // 在 action 返回或解决后的钩子
onError, // action 抛出或拒绝的钩子
}) => {
// 为这个特定的 action 调用提供一个共享变量
const startTime = Date.now()
// 这将在执行 "store "的 action 之前触发。
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 这将在 action 成功并完全运行后触发。
// 它等待着任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回一个拒绝的 promise,这将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动删除
unsubscribe()
在实际开发中,不可能把多个模块的数据都定义到一个store中,而是一个模块对应一个store,最后通过一个根store进行整合
// 模块一
import { defineStore } from 'pinia'
const useUserStore = defineStore('user', {
state: () => {
return {
name: 'haha',
age: 18,
}
},
})
export default useUserStore
// 模块二
import { defineStore } from 'pinia'
const useCounterStore = defineStore('user', {
state: () => {
return {
count: 1
}
},
})
export default useUserStore
import useUserStore from './user'
import useCounterStore from './counter'
// 统一导出useStore方法
export default function useStore() {
return {
user: useUserStore(),
counter: useCounterStore(),
}
}
<script setup>
import { storeToRefs } from 'pinia'
import useStore from './store'
const { counter } = useStore()
// 使用storeToRefs可以保证解构出来的数据也是响应式的
const { count } = storeToRefs(counter)
</script>
Pinia同样是一个Vue的状态管理工具,在Vuex的基础上提出了一些改进。与vuex相比,Pinia 最大的特点是:简便。
由于Pinea是轻量级的,体积很小,它适合于中小型应用。它也适用于低复杂度的Vue.js项目,因为一些调试功能,如时间旅行和编辑仍然不被支持。
将 Vuex 用于中小型 Vue.js 项目是过度的,因为它重量级的,对性能降低有很大影响。因此,Vuex 适用于大规模、高复杂度的 Vue.js 项目。
pinia和vuex在vue2和vue3都可以使用,一般来说vue2使用vuex,vue3使用pinia。
因篇幅问题不能全部显示,请点此查看更多更全内容