pinia

avatar
搬砖人

一、快速了解pinia

Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态,其实就是vuex5

官网:pinia.web3doc.top/

显著特点:

  • 支持 options api 和 composition api 两种语法创建 Store
  • 不再使用mutations
  • 没有嵌套模块,而是用组合的 stores 来代替
  • 对 ts 的支持
  • 体积小,仅1kb
  • 支持 Vue devtools、SSR 和 webpack 代码拆分

这里是 Pinia 官网提供的一个在线的 Pinia Demo,大家可以在上面学习。

pinia示意图:

vuex示意图

二、基本使用

安装

yarn add pinia
# 或者使用 npm
npm install pinia

如果您的应用使用 Vue 2,您还需要安装组合 API:@vue/composition-api

vue3中创建一个 pinia(根存储)并将其传递给应用程序:

import { createPinia } from 'pinia'
app.use(createPinia())

vue2的话还需要安装一个插件并将创建的 pinia 注入应用程序的根目录

import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)
const pinia = createPinia()

new Vue({
  el: '#app',
  // 其他选项...
  // ...
  // 注意同一个 `pinia` 实例可以在多个 Vue 应用程序中使用
  // 同一个页面
  pinia,
})

在 Vue 2 中,Pinia 使用 Vuex 的现有接口(因此不能与vuex一起使用)。

Store

定义一个store:使用defineStore()定义的,第一个参数是应用程序中store的唯一id

import { defineStore } from 'pinia'
export const useStore = defineStore('demo',{
    // other options
})

使用store

import { storeToRefs } from 'pinia'

export default defineComponent({ 
    setup() { 
        const store = useStore() 
        // ❌ 这不起作用,因为它会破坏响应式
        const { name } = store
        // 是响应式的
        const { age } = storeTorefs(store) 
        return { 
          // 一直会是 "eduardo"
          name, 
          // 这将是响应式的
          doubleValue: computed(() => store.doubleCount),
          age,
         }
      }, 
}) 

Pinia 不支持嵌套存储。相反,它允许你根据需要创建store。但是,store仍然可以通过在另一个store中导入和使用store来隐式嵌套

State

定义

import { defineStore } from 'pinia'
const useStore = defineStore('demo',{
    state:()=>({
        counter:0
    })
})

修改state

  1. 直接对state中的值进行修改
const store = useStore()
store.counter++
  1. $patch方法,使用使用部分state对象同时应用多个修改,也可以传入一个函数,批量进行处理
store. $patch({
  counter: store.counter + 1,
  name: 'Abalam',
})

store.$patch(state=>{
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})
  1. 替换整个state
store.$state = { counter: 666, name: 'Paimon'}
  1. 重置状态
store.$reset()
  1. 订阅状态

    可以通过 store 的 $subscribe() 方法查看状态及其变化,类似于 Vuex 的 subscribe方法。 与常规的 watch() 相比,使用 $subscribe() 的优点是subscriptions只会在patches之后触发一次。默认情况下,state subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 中)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 { detached: true } 作为第二个参数传递给detach当前组件的state subscription

    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 // 补丁对象传递给 to cartStore.$patch()
    
      // 每当它发生变化时,将整个状态持久化到本地存储
      localStorage.setItem('cart', JSON.stringify(state))
    },{ 
      // 此订阅将在组件卸载后保留
        detached: true 
    })
    

Getters

定义:可以类比计算属性,也可以通过 this 来直接访问store中的其他 gettters。 同样,getters 也具有缓存特性,同一个 getters 就算被调用多次,只要值不变,依旧只执行一次

import { defineStore } from 'pinia'
const useStore = defineStore('demo',{
    state:() => ({
        counter: 0
    })
    getters:{
        doubleCount: state => state.counter * 2,
        doubleNum(): number { 
            return this.counter * 2 + 1
        }
        // 当接收参数时,此时不具有缓存特性
        getUserById: (state) => {
            return (userId) => state.users.find((user) => user.id === userId)
        },
    }
})

访问其他getters:与计算属性一样,可以组合多个 getters,通过this.去访问其他getters。

export default defineStore('common', {
    getters: {
        // 通过state具体看代码
        doubleCount: (state) => state.count * 2,
        // 必须显示定义返回的类型,内部通过this访问store中的数据
        doubleCountPlus(): number {
            // autocompletion and typings for the whole store ✨
            return this .doubleCount * 2 + 1
        },
        otherGetter(state) {
        // 访问其他store的getters
            const otherStore = useOtherStore()
            return state.localData + otherStore.data
        },
    }
});

Actions

pinia中没有mutations。Store中的Actions 配置项可以执行同步或异步方法,且 action 被调用的是为常规的函数调用,而不是使用 dispatch 方法或 MapAction 辅助函数

定义

import { defineStore } from 'pinia'
const useStore = defineStore('demo',{
    state:() => ({
        counter: 0
    })
    actions:{
        increment() {
          this.counter++
        },
    }
})

actions 可以是异步的,您可以在其中await 任何 API 调用甚至其他操作!

import { defineStore } from 'pinia'
const useStore = defineStore('demo',{
    state:() => ({
        counter: 0
    })
    actions:{
        async registerUser(login, password) {
          try {
            this.userData = await api.post({ login, password })
          } catch (error) {
            showTooltip(error)
            // 让表单组件显示错误
            return error
          }
        },
        // 使用另一个store
        async fetchUserPreferences(preferences) {
          const auth = useAuthStore ()
          if (auth.isAuthenticated) {
            this.preferences = await fetchPreferences()
          } else {
            throw new Error('User must be authenticated')
          }
        },
    }
})

action 间的相互调用,直接用 this 访问即可。

订阅actions

  • 可以使用 store.$onAction() 订阅 action 及其结果。 传递给它的回调在 action 之前执行。after 处理 Promise 并允许您在 action 完成后执行函数。默认情况下,action subscriptions绑定到添加它们的组件(如果 store 位于组件的 setup() 内)。意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 true 作为第二个参数传递给当前组件的detach action subscription:

    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()
    

Plugins

定义:

    1.      Pinia 插件是一个函数,可以选择返回要添加到 store 的属性。 它需要一个可选参数,一个 *context*:
    1.  ```
        export function myPiniaPlugin(context) {
          context.pinia // 使用 `createPinia()` 创建的 pinia
          context.app // 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
          context.store // 插件正在扩充的 store
          context.options // 定义存储的选项对象传递给`defineStore()`
          // ...
        }
        ```

-   基本使用

-   ```
    import { createPinia } from 'pinia'

    // 为安装此插件后创建的每个store添加一个名为 `secret` 的属性
    // 这可能在不同的文件中
    function SecretPiniaPlugin() {
      return { secret: 'the cake is a lie' }
    }

    const pinia = createPinia()
    // 将插件提供给 pinia
    pinia.use(SecretPiniaPlugin)

    // 在另一个文件中
    const store = useStore()
    store.secret // 'the cake is a lie' 
    ```

-   通过一些底层API,可以对pinia store进行扩展:

    1.  向 Store 添加新属性
    1.  定义 Store 时添加新选项
    1.  为 Store 添加新方法
    1.  包装现有方法
    1.  更改甚至取消操作
    1.  实现本地存储等副作用
    1.  仅适用于特定 Store

数据持久化

插件 pinia-plugin-persist 可以辅助实现数据持久化功能。

  • 安装
    npm i pinia-plugin-persist
    
  • 使用
      import { createPinia } from 'pinia'
      import piniaPluginPersist from 'pinia-plugin-persist'
    
      const store = createPinia()
      store.use(piniaPluginPersist)
    
      export default store
    
  • 在对应的store开启persist即可
export const useUserStore = defineStore('user', {
// 开启数据缓存,数据默认存在 sessionStorage 里,并且会以 store 的 id 作为 key。
 persist: {
   enabled: true
 },
 state: () => {
   return {
     name: 'yunmu'
   }
 }
})
  • 自定义key值
persist: {
  enabled: true,
  strategies: [
    {
      key: 'userInfo',
      // 从sessionStorage转为localStorage
      storage: localStorage,
    }
  ]
}
  • 默认所有 state 都会进行缓存,可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
state: () => {
 return {
   name: 'yunmu',
   age: 18,
   gender: '男'
 }  
},
// 只持久存储name和age到localStorage
persist: {
 enabled: true,
 strategies: [
   {
     storage: localStorage,
     paths: ['name', 'age']
   }
 ]
}