Pinia (发音为 /pinj/,相似英文中的 “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) // ... },}