Pinia概览

Pinia 是一个拥有组合式 API 的 Vue 状态管理库,允许我们跨组件或页面共享状态。且同时支持 Vue2 和 Vue3,并不强制要求开发者使用组合式 API 。

支持以下功能:

  • Devtools支持
    • 追踪 actions、mutations 的时间线
    • 在组件中展示它们所用到的 Store
  • 热更新
    • 不必重载页面即可修改 Store
    • 开发时可保持当前的 State
  • 插件:可通过插件扩展 Pinia 功能
  • 为 JS 开发者提供适当的 TypeScript支持以及自动补全功能
  • 支持服务端渲染

使用

1.Store的定义

pinia同时支持 Vue2、Vue3

Options Store(Vue2)

与 Vue 的选项式 API 类似,我们也可以传入一个带有 stateactionsgetters 属性的 Option 对象

import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({count: 0}), // 返回一个对象,省略return,需要添加括号
getters: {
double: (state) => state.count * 2
},
actions: {
increment() {
this.count++;
}
}
})

Setup Store(Vue3)

与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象

import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
function increment() {
count.value++;
}

return { count, increment };
})

在 Setup Store中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数


注意:

store 是一个用 reactive 包装的对象,我们不能直接对其进行解构

<script setup>
const store = useCounterStore();
// ❌
// 直接对store进行解构,会丢失响应式, 与 props 类似
cosnt { counter, double } = store;
// ✅
// doubleValue 依然是响应式
const doubleValue = computed(() => store.double);
</script>

解决办法:

为了从 store 中解构属性时保持其响应性,需要使用 storeToRefs(),它将为每一个响应式属性创建引用。

<script setup>
import { storeToRefs } from 'pinia';
const store = useCounterStore();
// 使用storeToRefs进行解构,name 和 double 仍然就有响应式
const { name, double } = storeToRefs(store);
// increment 是 action 可以直接解构
const { increment } = store;
</script>

2.state

重置State

使用选项式API时,可以通过调用 store 的 $reset() 方法将 state 重置为初始值。

const store = useStore();
store.$reset();

Options API 用法

使用 mapState() 辅助函数将 state 属性映射为只读的计算属性:

只读属性,不可修改 state

import { mapState } from 'pinia';
import { useCounterStore } from '../stores/counter';

export default {
computed: {
...mapState(useCounterStore, ['count'])
// 或
...mapState(useCounterStore, {
// 将count映射为别名myOwnName
myOwnName: 'count',
double: store => store.count * 2,
magicValue(store) {
return store.someGetter + this.count + this.double
},
})
}
}

🚀 可修改的state

如果想修改 state 属性,可以使用 mapWritableState() 作为代替。但注意,不能像 mapState() 那样传递一个函数。

import { mapWritableState } from 'pinia';
import { useCounterStore } from '../stores/counter';

export default {
computed: {
...mapWritableState(useCounterStore, ['count']),
// 或
...mapWritableState(useCounterStore, {
myOwnName: 'count'
})
}
}

变更State

除了使用 store.count++ 这种方式修改 store 之外,还可以调用 $patch 方法,该方法允许用一个 state 的补丁对象更改多个属性:

store.$patch({
count: store.count + 1,
school: 'XYWZ',
position: 'china'
})

不过,用这种语法的话,有些变更真的很难实现或者很耗时:任何集合的修改(例如,向数组中添加、移除一个元素或是做 splice 操作)都需要你创建一个新的集合。因此,$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。

store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1});
state.hasChanged = true;
})

替换state

// 这实际上并没有替换 `$state`
store.$state = { count: 24 };
// 在它内部调用 `$patch()`
store.$patch({ count: 24 });

订阅State

通过 store 的 $subscribe 方法侦听state 及其变化

cartStore.$subscribe((mutation, state) => {
// const { type, storeId, payload } = mutation;
// type 'direct' | 'patch object' | 'patch function'
// storeId cart
// payload 传递给 cartStore.$patch() 的补丁对象

// 每当状态发生变化时,将整个state持久化到本地存储
localStorage.setItem('cart', JSON.stringify(state));
})

默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离。

<script setup>
const someStore = useSomeStore()
// 此订阅器即便在组件卸载之后仍会被保留
someStore.$subscribe(callback, { detached: true })
</script>