likes
comments
collection
share

Pinia极速通关!

作者站长头像
站长
· 阅读数 10

Pinia 是什么?

简单来说一个状态管理库与 vuex 的作用及定位一致。

同时支持 vue2、3(会有部分差异)最重要的一点是它支持 组合式API,也因为此它会比 vuex 更加灵活。

文中部分内容来自官网因为官网的例子非常好,本文讲解使用 Pinia 的基本过程及一些注意事项。

安装 Pinia

执行下方任意命令安装

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

在 main.js 中加入

import { createApp } from 'vue'
// 引入 pinia
import { createPinia } from 'pinia'
import App from './App.vue'

// 创建 pinia 实例
const pinia = createPinia()
const app = createApp(App)

// 注册 pinia 插件
app.use(pinia)
app.mount('#app')

Pinia 中的 Store

Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。

Store 分为两种 Option StoreSetup Store,使用方式并无区别。

Option Store

它有三个概念stategetter 和 action,我们可以假设这些概念相当于组件中的 data、 computed 和 methods

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

Setup Store

它可以使用 vue组合式API

Setup Storeref, reactive 会被识别为 statecomputed 会被识别为getter,函数会被识别为 action

import { ref } from 'vue';

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

  return { count, increment }
})

store的定义及使用

定义 store 需要一个独一无二的名字 defineStore('独一无二的名字', () => {})defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。即Option Store与

将返回的函数命名为 usexxxx  是一个符合组合式函数风格的约定。

可以定义任意多的 store,但为了让使用 pinia 的益处最大化(比如允许构建工具自动进行代码分割以及 TypeScript 推断),你应该在不同的文件中去定义 store

store 是一个用 reactive 包装的对象,直接结构会丧失响应性,应该使用 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?

一个 Store 应该包含可以在整个应用中访问的数据。这包括在许多地方使用的数据,例如显示在导航栏中的用户信息,以及需要通过页面保存的数据,例如一个非常复杂的多步骤表单。

就像眼镜一样

重置 store

通过调用 store 的 $reset() 方法将 state 重置为初始值。

监听 state 的变化

可以通过 store 的 $subscribe() 来实现

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

也可以在 pinia 实例上侦听整个 state。

watch(
  pinia.state,
  (state) => {
    // 每当状态发生变化时,将整个 state 持久化到本地存储。
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)

监听 action

可以通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。

在组件卸载后自动移除监听,但可以通过传入第二个参数使其保留 store.$onAction(callback, true)

after 表示在 promise 解决之后,允许你在 action 解决后执行一个一个回调函数。

onError 允许你在 action 抛出错误或 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()

组件外的使用

需要先注册 pinia 否则 store 会调用错误-官方示例

import { useUserStore } from '@/stores/user'
import { createApp } from 'vue'
import App from './App.vue'

// ❌  失败,因为它是在创建 pinia 之前被调用的
const userStore = useUserStore()

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

// ✅ 成功,因为 pinia 实例现在激活了
const userStore = useUserStore()

一个在 vue-router 中使用的示例 同样来自官方

import { createRouter } from 'vue-router'
const router = createRouter({
  // ...
})

// ❌ 由于引入顺序的问题,这将失败
const store = useStore()

router.beforeEach((to, from, next) => {
  // 我们想用这里的 store
  if (store.isLoggedIn) next()
  else next('/login')
})

router.beforeEach((to) => {
  // ✅ 这样做是可行的,因为路由器在安装完之后就会开始导航。
  // Pinia 也将被安装。
  const store = useStore()

  if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})

目录最佳实践

目录命名为 stores 下边有解释。store 文件存储在 modules 目录下 通过 index 统一导出。

Pinia极速通关!

import { createPinia } from 'pinia';  
import piniaPersist from 'pinia-plugin-persist';  
  
import useUserStore from './modules/useUserStore';  
import useShoppingCartStore from './modules/useShoppingCartStore';  
  
const pinia = createPinia();  
pinia.use(piniaPersist);  
  
// 这里统一导出使用  
export { useUserStore, useShoppingCartStore };  
export default pinia;

这样做的原因在于可以一次引入多个 store 使用,而不用每次都单独引入。


import useUserStore from '@/stores/modules/useUserStore';  
import useShoppingCartStore from '@/stores/modules/useShoppingCartStore';

// to

import { useUserStore, useShoppingCartStore } from '@/stores';

pinia 与 vuex 有何不同

  • pinia 没有 mutation
  • 无需要创建自定义的复杂包装器来支持 TypeScript,一切都可标注类型,API 的设计方式是尽可能地利用 TS 类型推理。
  • 无过多的魔法字符串注入,只需要导入函数并调用它们,然后享受自动补全的乐趣就好。
  • 无需要动态添加 Store 但仍然可以在任何时候手动使用一个 Store 来注册它。
  • 不再有嵌套结构的模块。你仍然可以通过导入和使用另一个 Store 来隐含地嵌套 stores 空间,虽然是 Pinia 从设计上提供的是一个扁平的结构,但仍然能够在 Store 之间进行交叉组合。你甚至可以让 Stores 有循环依赖关系
  • 不再有可命名的模块。考虑到 Store 的扁平架构,Store 的命名取决于它们的定义方式,你甚至可以说所有 Store 都应该命名。
  • Pinia 的目录一般被命名为 stores 而不是 store。这是为了强调 Pinia 可以使用多个 store,而不是 Vuex 的单一 store。
  • 在 Pinia 中不需要动态注册模块。store 设计之初就是动态的,只有在需要时才会被注册。如果一个 store 从未被使用过,它就永远不会被 “注册”。
  • 在 Vue 2 中,Pinia 使用的是 Vuex 的现有接口(因此不能与 Vuex 一起使用)。

总结

文章介绍了 pinia 的基本使用及一些注意事项。

pinia 的使用相对简单并没有太多可以写的点,文中所写算是一个学习的总结。

往期文章

转载自:https://juejin.cn/post/7267441882397016103
评论
请登录