• 本文来源于 Home | Pinia 中文文档 (web3doc.top)

1、概述

  • Pinia 是 Vue 的存储库,它容许您跨组件/页面共享状态。

1)比照 Vuex

  • 更小,只有1kb;
  • 更简略,actions 能够同时解决同步工作和异步工作;
  • 不再有命名空间模块,然而各个 store 之间互相独立并且能够相互援用。
  • 组合式 API 格调和良好的 TS 反对。

2)装置

  • Vue3 创立一个 pinia(根存储)并将其传递给应用程序

    import { createPinia } from 'pinia'app.use(createPinia())
  • Vue 2,您还须要装置一个插件并将创立的 pinia 注入应用程序的根目录
  • 须要装置组合 API:@vue/composition-api
import { createPinia, PiniaVuePlugin } from 'pinia'Vue.use(PiniaVuePlugin)const pinia = createPinia()new Vue({  el: '#app',  // ...  pinia,})

2、store

1)定义

  • Store 是应用 defineStore() 定义的,并且它须要一个惟一名称,作为第一个参数传递

    • 这个 name,也称为 id,是必要的,Pinia 应用它来将 store 连贯到 devtools。
    import { defineStore } from 'pinia'// useStore 能够是 useUser、useCart 之类的任何货色// 第一个参数是应用程序中 store 的惟一 idexport const useStore = defineStore('main', {  // other options...})

2)模块化

  • 能够依据须要定义任意数量的 store ,并且您应该在不同的文件中定义每个 store 以充分利用 pinia。

3、state

  • 在 Pinia 中,状态被定义为返回初始状态的函数。

    import { defineStore } from 'pinia'const useStore = defineStore('storeId', {  // 举荐应用 残缺类型推断的箭头函数  state: () => {    return {      // 所有这些属性都将主动推断其类型      counter: 0,      name: 'Eduardo',      isAdmin: true,    }  },})

1)拜访 “state”

  • 默认状况下,您能够通过 store 实例拜访状态来间接读取和写入状态
const store = useStore()store.counter++

2)扭转状态

  • 除了间接用 store.counter++ 批改 store,你还能够调用 $patch 办法。 它容许您应用局部“state”对象同时利用多个更改。

    store.$patch({  counter: store.counter + 1,  name: 'Abalam',})
  • $patch 办法也承受一个函数来批量批改汇合内局部对象的状况:

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

3)重置状态

  • 能够通过调用 store 上的 $reset() 办法将状态 重置 到其初始值
const store = useStore()store.$reset()

4)替换state

  • 能够通过将其 $state 属性设置为新对象来替换 Store 的整个状态:

    store.$state = { counter: 666, name: 'Paimon' }

5)订阅状态

  • 能够通过 store 的 $subscribe() 办法查看状态及其变动。
  • 与惯例的 watch() 相比,应用 $subscribe() 的长处是 subscriptions 只会在 patches 之后触发一次。
  • 当组件被卸载时,它们将被主动删除。 如果要在卸载组件后保留它们,请将 { detached: true } 作为第二个参数传递给 detach 以后组件的 state subscription

    export default {  setup() {    const someStore = useSomeStore()    // 此订阅将在组件卸载后保留    someStore.$subscribe(callback, { detached: true })    // ...  },}

4、getters

  • Getter 齐全等同于 Store 状态 的 computed

    • 箭头函数接管 state 作为第一个参数。
    • 惯例函数通过 this 拜访到整个 store 实例,然而须要定义返回类型。
    export const useStore = defineStore('main', {  state: () => ({    counter: 0,  }),  getters: {    // 主动将返回类型推断为数字    doubleCount(state) {      return state.counter * 2    },    // 返回类型必须明确设置    doublePlusOne(): number {      return this.counter * 2 + 1    },  },})

1)拜访其余 getter

  • 与计算属性一样,您能够组合多个 getter。 通过 this 拜访任何其余 getter。
export const useStore = defineStore('main', {  state: () => ({    counter: 0,  }),  getters: {    // 类型是主动推断的,因为咱们没有应用 `this`    doubleCount: (state) => state.counter * 2,    doubleCountPlusOne() {      return this.doubleCount + 1    },  },})

2)将参数传递给 getter

  • Getters 只是幕后的 computed 属性,因而无奈向它们传递任何参数。 然而,您能够从 getter 返回一个函数以承受任何参数:

    export const useStore = defineStore('main', {  getters: {    getUserById: (state) => {      return (userId) => state.users.find((user) => user.id === userId)    },  },})// App.vue<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)    },  },})

3)拜访其余 Store 的getter

  • 要应用其余存储 getter,您能够间接在 getter 外部应用它
import { useOtherStore } from './other-store'export const useStore = defineStore('main', {  state: () => ({    // ...  }),  getters: {    otherGetter(state) {      const otherStore = useOtherStore()      return state.localData + otherStore.data    },  },})

5、actions

  • Actions 相当于组件中的 methods,能够通过 this 拜访到整个 store 实例。
export const useStore = defineStore('main', {  state: () => ({    counter: 0,  }),  actions: {    increment() {      this.counter++    },    randomizeCounter() {      this.counter = Math.round(100 * Math.random())    },  },})
  • actions 能够是异步的,您能够在其中await 任何 API 调用甚至其余操作!
export const useUsers = defineStore('users', {  state: () => ({    userData: null,    // ...  }),  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      }    },  },})

1)拜访其余 store 操作

  • 要应用另一个 store ,您能够间接在 action 外部应用它:
import { useAuthStore } from './auth-store'export const useSettingsStore = defineStore('settings', {  state: () => ({    // ...  }),  actions: {    async fetchUserPreferences(preferences) {      const auth = useAuthStore()      if (auth.isAuthenticated) {        this.preferences = await fetchPreferences()      } else {        throw new Error('User must be authenticated')      }    },  },})

2)订阅 Actions

  • 能够应用 store.$onAction() 订阅 action 及其后果。 传递给它的回调在 action 之前执行。
  • after 解决 Promise 并容许您在 action 实现后执行函数。
  • onError 容许您在解决中抛出谬误。 这些对于在运行时跟踪谬误很有用 。
const unsubscribe = someStore.$onAction(  ({    name, // action 的名字    store, // store 实例    args, // 调用这个 action 的参数    after, // 在这个 action 执行结束之后,执行这个函数    onError, // 在这个 action 抛出异样的时候,执行这个函数  }) => {    // 记录开始的工夫变量    const startTime = Date.now()    // 这将在 `store` 上的操作执行之前触发    console.log(`Start "${name}" with params [${args.join(', ')}].`)    // 如果 action 胜利并且齐全运行后,after 将触发。    // 它将期待任何返回的 promise    after((result) => {      console.log(        `Finished "${name}" after ${          Date.now() - startTime        }ms.\nResult: ${result}.`      )    })    // 如果 action 抛出或返回 Promise.reject ,onError 将触发    onError((error) => {      console.warn(        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`      )    })  })// 手动移除订阅unsubscribe()
  • 当组件被卸载时,它们将被主动删除。 如果要在卸载组件后保留它们,请将 true 作为第二个参数传递给以后组件的 detach action subscription

    export default {  setup() {    const someStore = useSomeStore()    // 此订阅将在组件卸载后保留    someStore.$onAction(callback, true)    // ...  },}

6、plugins

  • 应用 pinia.use() 将插件增加到 pinia 实例中。
  • 装置此插件后创立的每个store增加一个名为 secret 的属性
import { createPinia } from 'pinia'// 这可能在不同的文件中function SecretPiniaPlugin() {  return { secret: 'the cake is a lie' }}const pinia = createPinia()// 将插件提供给 piniapinia.use(SecretPiniaPlugin)// 在另一个文件中const store = useStore()store.secret // 'the cake is a lie'
  • Pinia 插件是一个函数,能够抉择返回要增加到 store 的属性。 它须要一个可选参数,一个 context
export function myPiniaPlugin(context) {  context.pinia // 应用 `createPinia()` 创立的 pinia  context.app // 应用 `createApp()` 创立的以后应用程序(仅限 Vue 3)  context.store // 插件正在裁减的 store  context.options // 定义存储的选项对象传递给`defineStore()`  // ...}

1)增加新的内部属性

  • 当增加内部属性、来自其余库的类实例或仅仅是非响应式的货色时,您应该在将对象传递给 pinia 之前应用 markRaw() 包装对象。 这是一个将路由增加到每个 store 的示例:

    import { markRaw } from 'vue'// 依据您的路由所在的地位进行调整import { router } from './router'pinia.use(({ store }) => {  store.router = markRaw(router)})

2)在插件中调用 $subscribe

  • 您也能够在插件中应用 store.subscribestore.onAction

    pinia.use(({ store }) => {      store.$subscribe(() => {    // 在存储变动的时候执行  })      store.$onAction(() => {    // 在 action 的时候执行  })})

3)引入新的 store 属性

  • 向 store 增加新属性时,您还应该扩大 PiniaCustomProperties 接口。

    import 'pinia'declare module 'pinia' {  export interface PiniaCustomProperties {    // 通过应用 setter,咱们能够同时容许字符串和援用    set hello(value: string | Ref<string>)    get hello(): string    // 你也能够定义更简略的值    simpleNumber: number  }}
  • 而后能够平安地写入和读取它:

    pinia.use(({ store }) => {  store.hello = 'Hola'  store.hello = ref('Hola')  store.number = Math.random()  // @ts-expect-error: we haven't typed this correctly  store.number = ref(Math.random())})