Pinia (发音为 /piːnjʌ/,类似英文中的 “peenya”) 是最接近有效包名 piña(西班牙语中的 pineapple,即“菠萝”)的词。 菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。 与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。 它(菠萝)也是一种原产于南美洲的美味热带水果。
pinia作为vue官方推荐的状态管理器,先不说别的,看文档用一下再说
pinia的用法多少会和vuex有些不同,慢慢学习在找不一样的地方。
pinia优势
- 为 JS 开发者提供适当的 TypeScript 支持以及 autocompletion 功能。
- 支持服务端渲染
pinia使用
需要通过方法来定义状态,并且定义状态
import { defineStore } from 'pinia'
两种方法定义状态,一种为选项式,一种为函数式(文档中称呼与组件 setup() 类似)
// 选项式用法
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
actions: {
increment() {
this.count++
},
},
})
// 函数式用法
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
(可先略过)如果用选项式的话,Pinia 也提供了一组类似 Vuex 的 map helpers。你可以用和之前一样的方式来定义 Store,然后通过 mapStores()、mapState() 或 mapActions()使用
const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
const useUserStore = defineStore('user', {
// ...
})
export default {
computed: {
// 其他计算属性
// ...
// 允许访问 this.counterStore 和 this.userStore
...mapStores(useCounterStore, useUserStore)
// 允许读取 this.count 和 this.double
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
// 允许读取 this.increment()
...mapActions(useCounterStore, ['increment']),
},
}
之后需要在入口文件main.js里面注入状态
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
什么是Store并且什么时候用Store就不在这里赘述了。
定义store
export const useStore = defineStore('main', {
// 其他配置...
})
这个 name ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 use... 是一个符合组合式函数的惯例。
defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。
选项式定义store
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
你可以认为 state 是 store 的数据(data),getters 是 store 的计算属性(computed),而 actions 则是方法(methods)
为方便上手使用,选项式 Store 应尽可能直观简单。
函数式定义store
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
在 Setup Stores 中:
ref()s 就是 state 属性
computed()s 就是 getters
function()s 就是 actions
Setup stores 比 Options Stores 带来了更多的灵活性,因为你可以在一个 store 内创建 watchers,并自由地使用任何组合式函数。然而,请记住,使用组合式函数会让 SSR 变得更加复杂。
建议先使用选项式熟悉pinia。
请注意,store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value,就像 setup 中的 props 一样,如果你写了,我们也不能解构它:
export default defineComponent({
setup() {
const store = useCounterStore()
// ❌ 这将无法生效,因为它破坏了响应式
// 这与从 `props` 中解构是一样的。
const { name, doubleCount } = store
name // "eduardo"
doubleCount // 2
return {
// 始终是 "eduardo"
name,
// 始终是 2
doubleCount,
// 这个将是响应式的
doubleValue: computed(() => store.doubleCount),
}
},
})
为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。
import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const store = useCounterStore()
// `name` and `doubleCount` 都是响应式 refs
// 这也将为由插件添加的属性创建 refs
// 同时会跳过任何 action 或非响应式(非 ref/响应式)属性
const { name, doubleCount } = storeToRefs(store)
// 名为 increment 的 action 可以直接提取
const { increment } = store
return {
name,
doubleCount,
increment,
}
},
})
store中的state
在大多数情况下,state 都是你的 store 的核心。人们通常会首先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。
默认情况下,你可以通过 store 实例访问 state,直接对其进行读写。
你可以通过调用 store 的 $reset() 方法将 state 重置为初始值, 比如说在推出登录或者退出一些模块的时候可以使用该方法。
const store = useStore()
store.$reset()
如果你想修改这些 state 属性(例如,如果你有一个表单),你可以使用 mapWritableState() 来代替。但注意你不能像 mapState() 那样传递一个函数 (后期再结合例子整理)
修改state中的值
store.count++ // 第一种方法,直接修改
// 第二种方法 你还可以调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性:
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
然而,用这种$patch语法的话,有些变更真的很难实现或者很耗时:任何集合的修改(例如,从数组中推送、移除、拼接一个元素)都需要你创建一个新的集合。因此,**$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更**。
cartStore.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
你不能完全替换掉 store 的 state,因为那会破坏响应性。但是,你可以 patch 它。
// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })
订阅state
类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法观测 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次(例如,当使用上面的函数版本时)。
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// 和 cartStore.$id 一样
mutation.storeId // 'cart'
// 只有 mutation.type === 'patch object'的情况下才可用
mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('cart', JSON.stringify(state))
})
默认情况下,state subscriptions 会被绑定到添加它们的组件上(如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中 detach:
export default {
setup() {
const someStore = useSomeStore()
// 在组件被卸载后,该订阅依旧会被保留。
someStore.$subscribe(callback, { detached: true })
// ...
},
}
store中的getter
Getter 完全等同于 store 的 state 的计算值。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:
export const useStore = defineStore('main', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
})
- 可以访问其他getters
export const useStore = defineStore('main', {
state: () => ({
count: 0,
}),
getters: {
// 类型是自动推断出来的,因为我们没有使用 `this`
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
// 自动补全 ✨
return this.doubleCount + 1
},
},
})
- 可以向getter传递参数
export const useStore = defineStore('main', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
组件中使用
<script>
export default {
setup() {
const store = useStore()
return { getUserById: store.getUserById }
},
}
</script>
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>
请注意,当你这样做时,getter 将不再被缓存,它们只是一个被你调用的函数。不过,你可以在 getter 本身中缓存一些结果,虽然这种做法并不常见,但可以证明的是它的性能会更好:
export const useStore = defineStore('main', {
getters: {
getActiveUserById(state) {
const activeUsers = state.users.filter((user) => user.active)
return (userId) => activeUsers.find((user) => user.id === userId)
},
},
})
store中的action
Actions 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。
类似 getter,action 也可通过 this 访问整个 store 实例,并支持完整的类型约束(以及自动补全✨)。不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action!下面是一个使用 Mande 的例子。请注意,你使用什么库并不重要,只要你得到的是一个Promise,你甚至可以使用原生 fetch 函数(在浏览器中):
所有的异步请求可以在action里面编写
...
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// 让表单组件显示错误
return error
}
},
},
...
订阅action
你可以通过 store.$onAction() 来监测 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after 表示在 promise 解决之后,允许你在 action 解决后执行一个一个回调函数。同样地,onError 允许你在 action 抛出错误或 reject 时执行一个回调函数。这些函数对于追踪运行时错误非常有用,类似于Vue docs 中的这个提示。
这里有一个例子,在运行 action 之前以及 action resolve/reject 之后打印日志记录。
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()
默认情况下,action 订阅器 会被绑定到添加它们的组件上(如果 store 在组件的 setup() 内)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 true 作为第二个参数传递给 action 订阅器,以便将其从当前组件中剥离(detach):
export default {
setup() {
const someStore = useSomeStore()
// 在组件被卸载后,这个订阅依旧会被保留。
someStore.$onAction(callback, true)
// ...
},
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。