【关于Pinia与Vuex】

关于Pinia与Vuex

前言:什么时候使用状态管理?

对于简单组件,我们要实现状态共享可以提取状态到父组件,但是页面较复杂时,你会发现跨组件状态共享变得力不从心。比如:

  • 组件层级太深,需要共享状态,此时状态要层层传递。
  • 子组件更新一个状态,可能有多个父组件,兄弟组件共用,实现困难。

这种情况下继续使用 “提取状态到父组件” 的方法你会发现很复杂。而且随着组件增多,嵌套层级加深,这个复杂度也越来越高。因为关联的状态多,传递复杂,很容易出现像某个组件莫名其妙的更新,某个组件死活不更新这样的问题,异常排查也会困难重重。为了解决这些问题,Vue 提供 Vuex

状态管理Vuex,其实可以理解为全局状态管理,这里的状态不同于组件内部的状态,它是独立于组件单独维护的,然后再通过某种方式与需要该状态的组件关联起来。

PiniaVuex 都是Vue.js 应用程序的全局状态管理工具。

由于 Vuex 4.x 版本只是个过渡版,Vuex 4 对 TypeScript 和 Composition API 都不是很友好,虽然官方团队在 GitHub 已有讨论 Vuex 5 的开发提案,但从 2022-02-07 在 Vue 3 被设置为默认版本开始, Pinia 已正式被官方推荐作为全局状态管理的工具。

为什么要使用 Pinia?

Pinia (发音为 /piːnjʌ/)支持 Vue 3 和 Vue 2 ,对 TypeScript 也有很完好的支持,与 Vuex 相比,Pinia 提供了一个更简单的 API,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。状态管理弃用了 vuex 而采用Pinia 的 5个重要条件:

  • Pinia 的 API 设计非常接近 Vuex 5 的提案。(作者是 Vue 核心团队成员)
  • 无需像 Vuex 4 自定义复杂的类型来支持 typescript,天生具备完美的类型推断。
  • 模块化设计,你引入的每一个 store 在打包时都可以自动拆分他们。
  • 无嵌套结构,但你可以在任意的 store 之间交叉组合使用。
  • Pinia 与 Vue devtools 挂钩,不会影响 Vue 3 开发体验。

与 Vuex 3.x/4.x 的比较

Pinia API 与 Vuex ≤4 有很大不同,即:

  • mutations 不再存在。他们经常被认为是非常冗长。他们最初带来了 devtools 集成,但这不再是问题。
  • 更好的TypeScript支持。无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
  • 不再需要注入、导入函数、调用函数、享受自动完成功能!
  • 无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手 动使用 Store 进行注册,但因为它是自动的,您无需担心。
  • 不再有 modules 的嵌套结构。您仍然可以通过在另一个 Store 中导入和 使用 来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。 您甚至可以拥有 Store 的循环依赖关系。
  • 没有命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说 所有 Store 都是命名空间的。

有关如何将现有 Vuex ≤4 项目转换为使用 Pinia 的更详细说明,请参阅 从Vuex 迁移指南

状态树的结构

作用Vue ComponentVuexPinia
数据管理datastatestate
数据计算computedgettersgetters
行为方法methodsmutations / actionsactions

可以看到 Pinia 的结构和用途都和 Vuex 与 Component 非常相似,并且 Pinia 相对于 Vuex ,在行为方法部分去掉了 mutations (同步操作)和 actions (异步操作)的区分,更接近组件的结构,入门成本会更低一些。

安装和启用

Pinia 目前还没有被广泛的默认集成在各种脚手架里,所以如果你原来创建的项目没有 Pinia ,则需要手动安装它。

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

查看你的package.json,看看里面的 dependencies 是否成功加入了 Pinia 和它的版本号(下方是示例代码,以实际安装的最新版本号为准):

{
  "dependencies": {
    "pinia": "^2.0.12"
  },
}

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

import { createPinia } from 'pinia'

app.use(createPinia())

如果您使用的是 Vue 2,您还需要安装一个插件并将创建的 pinia 注入应用程序的根目录:

import { createPinia, PiniaVuePlugin } from 'pinia'

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

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

Store

例如导航栏中显示的用户信息,以及需要通过页面保留的数据,例如一个非常复杂的多步骤表格,这些时候你可能需要用到Store。

定义Store

和 Vuex 一样, Pinia 的核心也是称之为 Store 。参照 Pinia 官网推荐的项目管理方案,我们也是先在 src 文件夹下创建一个 stores 文件夹,并在里面添加一个 index.ts 文件,然后我们就可以来添加一个最基础的 Store 。

目录结构:

# Pinia ------------------------------------------------------------------------------------
src
└── stores
    ├── index.js          # (Optional) Initializes Pinia, does not import stores
    ├── module1.js        # 'module1' id
    ├── nested-module2.js # 'nested/module2' id
    ├── nested-module3.js # 'nested/module3' id
    └── nested.js         # 'nested' id


# Vuex -------------------------------------------------------------------------------------
src
└── store
    ├── index.js           # Initializes Vuex, imports modules
    └── modules
        ├── module1.js     # 'module1' namespace
        └── nested
            ├── index.js   # 'nested' namespace, imports module2 & module3
            ├── module2.js # 'nested/module2' namespace
            └── module3.js # 'nested/module3' namespace

Pinia 的目录被称为stores而不是store。这是为了强调 Pinia 使用多个store,便于理解

Store 是通过 defineStore 方法来创建的,它有两种入参形式:

形式 1 :接收两个参数

接收两个参数,第一个参数是 Store 的唯一 ID (即容器名称:可根据具体业务命名),第二个参数是 Store 的选项:

// Pinia
import { defineStore } from 'pinia'

export const useStore = defineStore('main', {
  /*
  *	类似组件的data,用于存储全局状态
  */
 	state:()=>{
    return {}
  },
   /*
  *	类似组件的computed,用来封装计算属性
  */
  getters:{
    
  },
  /*
  *	类似组件的methods,封装修改逻辑,修改state
  */
  actions:{}
})





// Vuex

形式 2 :接收一个参数
import { defineStore } from 'pinia'

export const useStore = defineStore({
  id: 'main',
  state:()=>{
    return {}
  },
  getters:{},
  actions:{}
})

TIP

不论是哪种创建形式,都必须为 Store 指定一个唯一ID,Pinia会把所有容器挂载到根容器

添加 state

大多数时候,state 是 store 的核心部分。 我们通常从定义应用程序的状态开始。 在 Pinia 中,状态被定义为返回初始状态的函数。

import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // 推荐使用 完整类型推断的箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断其类型
      counter: 0,
      name: 'Eduardo',
      isAdmin: true,
    }
  },
})



如果您使用的是 Vue 2,您在 state 中创建的数据遵循与 Vue 实例中的 data 相同的规则,即 state 对象必须是普通的,并且您需要在以下情况下调用 Vue.set() 为其添加新的属性。

访问 state✨

用法上和 Vuex 很相似,但有一点区别是,数据直接是挂在 store 上的,而不是 store.state 上面!

默认情况下,您可以通过 store 实例访问状态来直接读取和写入状态:

const store = useStore()
store.counter++

TIP

Vuex 是 store.state.counter++ , Pinia 是 store.counter++

更新state

除了直接用 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
})

这里的主要区别是$patch() 允许您将批量更改的日志写入开发工具中的一个条目中。 注意两者,state 和 $patch() 的直接更改都出现在 devtools 中,并且可以进行 time travelled(在 Vue 3 中还没有)。

替换 state

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

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

您还可以通过更改 pinia 实例的 state 来替换应用程序的整个状态。 这在 SSR for hydration 期间使用。

pinia.state.value = {}

重置state

您可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:

const store = useStore()
store.$reset()

订阅 state

可以通过 store 的 $subscribe() 方法查看状态及其变化,类似于 Vuex 的 subscribe 方法。 与常规的 watch() 相比,使用$subscribe()的优点是 subscriptions 只会在 patches 之后触发一次

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

默认情况下,state subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 中)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 { detached: true } 作为第二个参数传递给 detach 当前组件的 state subscription

export default {
  setup() {
    const someStore = useSomeStore()

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

getters

添加getter

Getter 完全等同于 Store 状态的 计算值。 它们可以用 defineStore() 中的 getters 属性定义。 他们接收state作为第一个参数以鼓励箭头函数的使用:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
})

大多数时候,getter 只会依赖状态,但是,他们可能需要使用其他 getter。 正因为如此,我们可以在定义常规函数时通过 this 访问到 整个 store 的实例, 但是需要定义返回类型(在 TypeScript 中)。 这是由于 TypeScript 中的一个已知限制,并且不会影响使用箭头函数定义的 getter,也不会影响不使用 this 的 getter:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // 自动将返回类型推断为数字
    doubleCount(state) {
      return state.counter * 2
    },
    // 返回类型必须明确设置
    doublePlusOne(): number {
      return this.counter * 2 + 1
    },
  },
})

然后你可以直接在 store 实例上访问 getter:

<template>
  <p>Double count is {{ store.doubleCount }}</p>
</template>

<script>
export default {
  setup() {
    const store = useStore()

    return { store }
  },
}
</script>

访问其他 getter

与计算属性一样,您可以组合多个 getter。 通过 this 访问任何其他 getter。 即使您不使用 TypeScript,您也可以使用 JSDoc 提示您的 IDE 类型:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // 类型是自动推断的,因为我们没有使用 `this`
    doubleCount: (state) => state.counter * 2,
    // 这里需要我们自己添加类型(在 JS 中使用 JSDoc)。 我们还可以
    // 使用它来记录 getter
    /**
     * 返回计数器值乘以二加一。
     *
     * @returns {number}
     */
    doubleCountPlusOne() {
      // 自动完成 ✨
      return this.doubleCount + 1
    },
  },
})

将参数传递给 getter

Getters 只是幕后的 computed 属性,因此无法向它们传递任何参数。 但是,您可以从 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 的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
    },
  },
})

与 setup() 一起使用

您可以直接访问任何 getter 作为 store 的属性(与 state 属性完全一样):

export default {
  setup() {
    const store = useStore()

    store.counter = 3
    store.doubleCount // 6
  },
}

使用选项 API

对于以下示例,您可以假设已创建以下 store:

// Example File Path:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia',

const useCounterStore = defineStore('counterStore', {
  state: () => ({
    counter: 0
  }),
  getters: {
    doubleCounter() {
      return this.counter * 2
    }
  }
})
使用setup()

虽然Composition API并不适合所有人,但setup()钩子可以使在 Options API 中使用 Pinia 更容易。 不需要额外的 map helpers 功能!

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  computed: {
    quadrupleCounter() {
      return counterStore.doubleCounter * 2
    },
  },
}
没有setup()

您可以使用 previous section of state 中使用的相同 mapState() 函数映射到 getter:

import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // 允许访问组件内的 this.doubleCounter
    // 与从 store.doubleCounter 中读取相同
    ...mapState(useCounterStore, ['doubleCount'])
    // 与上面相同,但将其注册为 this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'doubleCounter',
      // 您还可以编写一个访问 store 的函数
      double: store => store.doubleCount,
    }),
  },
}

actions

添加action

Actions 相当于组件中的 methods。 它们可以使用 defineStore() 中的 actions 属性定义,并且它们非常适合定义业务逻辑:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    },
    randomizeCounter() {
      this.counter = Math.round(100 * Math.random())
    },
  },
})

与 getters 一样,操作可以通过 this 访问 整个 store 实例并提供完整类型(和自动完成)支持。 与它们不同,actions 可以是异步的,您可以在其中await 任何 API 调用甚至其他操作! 这是使用 Mande 的示例。 请注意,只要您获得“Promise”,您使用的库并不重要,您甚至可以使用浏览器的“fetch”函数:

import { mande } from 'mande'

const api = mande('/api/users')

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

你也可以完全自由地设置你想要的任何参数并返回任何东西。 调用 Action 时,一切都会自动推断!

Actions 像 methods 一样被调用:

export default defineComponent({
  setup() {
    const main = useMainStore()
    // Actions 像 methods 一样被调用:
    main.randomizeCounter()

    return {}
  },
})

访问其他 store 操作

要使用另一个 store ,您可以直接在操作内部使用它:

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

与 setup() 一起使用

您可以直接调用任何操作作为 store 的方法:

export default {
  setup() {
    const store = useStore()

    store.randomizeCounter() //调用useStore中action的randomizeCounter()
  },
}

TIP

在 Pinia ,调用action不需要和 Vuex 一样执行 commit 或者 dispatch,像普通的函数一样使用即可,

使用选项 API

对于以下示例,您可以假设已创建以下 store:

// Example File Path:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia',

const useCounterStore = defineStore('counterStore', {
  state: () => ({
    counter: 0
  }),
  actions: {
    increment() {
      this.counter++
    }
  }
})
使用setup()

虽然 Composition API 并不适合所有人,但 setup() 钩子可以使在 Options API 中使用 Pinia 更容易。 不需要额外的 map helpers 功能!

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  methods: {
    incrementAndPrint() {
      counterStore.increment()
      console.log('New Count:', counterStore.count)
    },
  },
}
不使用 setup()

如果您根本不想使用 Composition API,可以使用 mapActions() 将操作属性映射为组件中的方法:

import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  methods: {
    // gives access to this.increment() inside the component
    // same as calling from store.increment()
    ...mapActions(useCounterStore, ['increment'])
    // same as above but registers it as this.myOwnName()
    ...mapActions(useCounterStore, { myOwnName: 'doubleCounter' }),
  },
}

订阅 Actions

可以使用 store.$onAction() 订阅 action 及其结果。 传递给它的回调在 action 之前执行。 after 处理 Promise 并允许您在 action 完成后执行函数。 以类似的方式,onError 允许您在处理中抛出错误。 这是一个在运行 action 之前和它们 resolve/reject 之后记录的示例。

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

默认情况下,action subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 内)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 true 作为第二个参数传递给当前组件的 detach action subscription

export default {
  setup() {
    const someStore = useSomeStore()

    // 此订阅将在组件卸载后保留
    someStore.$onAction(callback, true)

    // ...
  },
}

添加多个 Store

到这里,对单个 Store 的配置和调用相信都已经清楚了,实际项目中会涉及到很多数据操作,还可以用多个 Store 来维护不同需求模块的数据状态。

TIP

多个Store 和 Vuex 的 Module比较相似,目的都是为了避免状态树过于臃肿,但用起来会更为简单。

目录结构建议

建议统一存放在 src/stores 下面管理,根据业务需要进行命名,比如 user 就用来管理登录用户相关的状态数据。

src
└─stores
  │ # 入口文件
  ├─index.ts
  │ # 多个 store
  ├─user.ts
  ├─game.ts
  └─news.ts

里面暴露的方法就统一以 use 开头加上文件名,并以 Store 结尾,作为小驼峰写法,比如 user 这个 Store 文件里面导出的函数名就是:

// src/stores/user.ts
export const useUserStore = defineStore('user', {
  // ...
})

然后以 index.ts 里作为统一的入口文件, index.ts 里的代码写为:

export * from './user'
export * from './game'
export * from './news'

这样在使用的时候,只需要从 @/stores 里导入即可,无需写完整的路径,例如,只需要这样:

import { useUserStore } from '@/stores'

而无需这样:

import { useUserStore } from '@/stores/user'

在 Vue 组件 / TS 文件里使用

这里我以一个比较简单的业务场景举例,希望能够方便的理解如何同时使用多个 Store 。

假设目前有一个 userStore 是管理当前登录用户信息, gameStore 是管理游戏的信息,而 “个人中心” 这个页面需要展示 “用户信息” ,以及 “该用户绑定的游戏信息”,那么就可以这样:

import { defineComponent, onMounted, ref } from 'vue'
import { storeToRefs } from 'pinia'
// 这里导入你要用到的 Store
import { useUserStore, useGameStore } from '@/stores'
import type { GameItem } from '@/types'

export default defineComponent({
  setup() {
    // 先从 userStore 获取用户信息(已经登录过,所以可以直接拿到)
    const userStore = useUserStore()
    const { userId, userName } = storeToRefs(userStore)

    // 使用 gameStore 里的方法,传入用户 ID 去查询用户的游戏列表
    const gameStore = useGameStore()
    const gameList = ref<GameItem[]>([])
    onMounted(async () => {
      gameList.value = await gameStore.queryGameList(userId.value)
    })

    return {
      userId,
      userName,
      gameList,
    }
  },
})

解构store会破坏响应,需要解构时可引用storeToRefs进行操作,会将state响应式处理

再次提醒,切记每个 Store 的 ID 必须不同,如果 ID 重复,在同一个 Vue 组件 / TS 文件里定义 Store 实例变量的时候,会以先定义的为有效值,后续定义的会和前面一样。

如果先定义了 userStore :

// 假设两个 Store 的 ID 一样
const userStore = useUserStore()  // 是想要的 Store
const gameStore = useGameStore()  // 得到的依然是 userStore 的那个 Store
如果先定义了 gameStore :

// 假设两个 Store 的 ID 一样
const gameStore = useGameStore()  // 是想要的 Store
const userStore = useUserStore()  // 得到的依然是 gameStore 的那个 Store

Store 之间互相引用

如果在定义一个 Store 的时候,要引用另外一个 Store 的数据,也是很简单,回到那个 message 的例子,我们添加一个 getter ,它会返回一句问候语 ‘欢迎用户’ :

// src/stores/message.ts
import { defineStore } from 'pinia'

// 导入用户信息的 Store 并启用它
import { useUserStore } from './user'
const userStore = useUserStore()

export const useMessageStore = defineStore('message', {
  state: () => ({
    message: 'Hello World',
  }),
  getters: {
    // 这里我们就可以直接引用 userStore 上面的数据了
    greeting: () => `Welcome, ${userStore.userName}!`,
  },
})

假设现在 userName 是 Petter ,那么你会得到一句对 Petter 的问候:

const messageStore = useMessageStore()
console.log(messageStore.greeting)  // Welcome, Petter!

专属插件的使用

Pinia 拥有非常灵活的可扩展性,有专属插件可以开箱即用满足更多的需求场景。

如何查找插件

插件有统一的命名格式 pinia-plugin-* ,所以你可以在 npmjs 上搜索这个关键词来查询目前有哪些插件已发布。

点击查询: pinia-plugin - npmjs

如何使用插件

这里以pinia-plugin-persistedstate为例,这是一个让数据持久化存储的 Pinia 插件。

安装
yarn add pinia-plugin-persistedstate
npm install pinia-plugin-persistedstate

TIP

数据持久化存储,指页面关闭后再打开,浏览器依然可以记录之前保存的本地数据,例如:浏览器原生的 localStorage 和 IndexedDB ,或者是一些兼容多种原生方案并统一用法的第三方方案,例如: localForage 。

插件也是独立的 npm 包,需要先安装,再激活,然后才能使用。

激活方法会涉及到 Pinia 的初始化过程调整,这里不局限于某一个插件,通用的插件用法如下(请留意代码注释):

// src/main.ts
import { createApp } from 'vue'
import App from '@/App.vue'
import { createPinia } from 'pinia' // 导入 Pinia
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 导入 Pinia 插件

const pinia = createPinia() // 初始化 Pinia
pinia.use(piniaPluginPersistedstate) // 激活 Pinia 插件

createApp(App)
  .use(pinia) // 启用 Pinia ,这一次是包含了插件的 Pinia 实例
  .mount('#app')
使用前

Pinia 默认在页面刷新时会丢失当前变更的数据,没有在本地做持久化记录:

// 其他代码省略
const store = useMessageStore()

// 假设初始值是 Hello World
setTimeout(() => {
  // 2s 后变成 Hello World!
  store.message = store.message + '!'
}, 2000)

// 页面刷新后又变回了 Hello World
使用后

按照 persistedstate 插件的文档说明,我们在其中一个 Store 启用它,只需要添加一个 persist: true 的选项即可开启:

// src/stores/message.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'

const userStore = useUserStore()

export const useMessageStore = defineStore('message', {
  state: () => ({
    message: 'Hello World',
  }),
  getters: {
    greeting: () => `Welcome, ${userStore.userName}`,
  },
  // 这是按照插件的文档,在实例上启用了该插件,这个选项是插件特有的
  persist: true,
})

回到我们的页面,现在这个 Store 具备了持久化记忆的功能了,它会从 localStorage 读取原来的数据作为初始值,每一次变化后也会将其写入 localStorage 进行记忆存储。

// 其他代码省略
const store = useMessageStore()

// 假设初始值是 Hello World
setTimeout(() => {
  // 2s 后变成 Hello World!
  store.message = store.message + '!'
}, 2000)

// 页面刷新后变成了 Hello World!!
// 再次刷新后变成了 Hello World!!!
// 再次刷新后变成了 Hello World!!!!

你可以在浏览器查看到 localStorage 的存储变化,以 Chrome 浏览器为例,按 F12 ,打开 Application 面板,选择 Local Storage ,可以看到以当前 Store ID 为 Key 的存储数据。

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Pinia与Vuex是两种用于状态管理的库。Pinia是一个相对较新的库,它侧重于TypeScript风格的开发方式,提供了更好的维护性和易读性。相比之下,Vuex是一个经过长时间稳定运行的库,具有完善的文档和社区支持。 在功能方面,Vuex提供了更为完整和复杂的接口和功能集,包括管理多个模块、插件等。而Pinia则更加简单,只提供了核心需求,如片段式组装store。 虽然Pinia可以替代Vuex的一些功能,但这并不意味着一定要使用Pinia来代替Vuex。选择使用哪个库取决于你的具体需求、项目的规模和团队的技术栈。如果你需要更多的功能和复杂性,同时依赖于已有的文档和社区支持,那么Vuex可能是更合适的选择。如果你更注重维护性和易读性,并且倾向于使用TypeScript,那么Pinia可能是更好的选择。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [在vue中使用pinia,并且保持数据持久化](https://download.csdn.net/download/shaoyahu/87691975)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [vuex和pinia](https://blog.csdn.net/zhaojjjjjj163/article/details/131023492)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值