Pinia 状态管理

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。

Pinia 起源于一次探索 Vuex 下一个迭代的实验,因此结合了 Vuex 5 核心团队讨论中的许多想法。最后,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分功能,所以决定将其作为新的推荐方案来代替 Vuex。

Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的,Pinia 可以同时支持 Vue2 和 Vue3。

 1.1 安装
# 或者使用其它包管理工具,如 yarn pnpm
npm install pinia

创建一个 pinia 实例 (根 store) 并将其传递给应用:

// main.js
import { createSSRApp } from 'vue'
// 导入 Pinia
import { createPinia } from 'pinia'

import App from './App'
import '@/utils/utils'

export function createApp() {
  const app = createSSRApp(App)
  // 创建 Pinia 实例
  const pinia = createPinia()
  app.use(pinia)
  
  return {
    app,
  }
}

注意事项:

  • createSSRApp 是配合 SSR 来使用的,其用法与 createApp 相同,在这里 uni-app 为了做跨平台开发所采取的方式,其作用我们就按 createApp 来理解即可。
1.2 Store

在深入研究核心概念之前,我们得知道 Store 是用 defineStore() 定义的,它支持两种语法法格式,分别是选项式 Store 和 组件式 Store,创建一个文件来演示它的使用:

  1. 选项式(Options) Store
// stores/counter.js
import { defineStore } from 'pinia'

// 选项式 Store
export const useCounterStore = defineStore('counter', {
  state: () => {
    return {
      count: 0,
    }
  },
  getters: {
    double: (state) => {
      return state.count * 2
    },
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
  },
})

pages/test/index 页面中通一个计数器来测试它的用法,下面新增的布局相关代码

<!-- pages/test/index.vue -->
<script setup>
  import { http } from '@/utils/http.js'

  // 测试网络请求
  async function onButtonClick() {
		// 省略前面小节代码
  }
</script>

<template>
  <view class="content">
    <!-- 前面小节代码省略了 -->
    <view class="counter">
      <button class="button" type="primary">-</button>
      <input class="input" type="text" />
      <button class="button" type="primary">+</button>
    </view>
  </view>
</template>
<style lang="scss">
  // 前面小节代码省略了
  .counter {
    display: flex;
    margin-top: 30rpx;
  }

  .input {
    flex: 1;
    height: 96rpx;
    text-align: center;
    border: 2rpx solid #eee;
    box-sizing: border-box;
  }

  .button {
    width: 100rpx;
    margin: 0;

    &:first-child {
      border-start-end-radius: 0;
      border-end-end-radius: 0;
    }
    &:last-child {
      border-start-start-radius: 0;
      border-end-start-radius: 0;
    }
  }
</style>

接下来看如何使用 Pinia,在使用时要注意必须要调用定义好的 Store 才会真正创建 Store 实例,对应到下面的代码是必须要调用 useCounterStore 后才会创建 Store 实例,即 counterStore

<!-- pages/test/index.vue -->
<script setup>
  import { http } from '@/utils/http.js'
  // 导入定义好的 Store
  import { useCounterStore } from '@/stores/counter.js'
  // 创建 Store 实例
  const counterStore = useCounterStore()

  // 测试网络请求
  async function onButtonClick() {
		// 省略前面小节代码
  }
</script>

<template>
  <view class="content">
    <!-- 前面小节代码省略了 -->
    <view class="counter">
      <button class="button" type="primary">-</button>
      <input class="input" type="text" />
      <button class="button" type="primary">+</button>
    </view>
  </view>
</template>

Store 实例中定义的 stategettersactions 可以直接应用到组件模板当中。

注意事项:

  • 定义 Store 时建议(非必须)使用  use + 名称 + Store 格式命名,其中名称也会被当做 ID 出现在调试工具中
  • 创建 Store 实例时,实例的名称建议用 名称 + Store 格式命名,避免引入多个 Store 时名称重复的问题
  1. 组件式(Setup)Store

组合式 Store 用法与选项式 Store 用法最直接的区别就是 defineStore 的第 2 个参数传入是一个函数,而选项式 Store 传入的是一个对象。

另一个区别是在组合式 API 中允许使用 Vue 的组合式函数,如 refcomputedwatch 等。

在组件式 Store 中:

  • ref() 就是 state 属性
  • computed 就是 getters
  • function() 就是 actions
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// 1. 选项式 Store

// 这里省略上一小节代码

// 2. 组合式 Store
export const useCounterStore = defineStore('counter', () => {
  // 定义 state
  const count = ref(0)
  
  // 定义 getters
  const double = computed(() => count.value * 2)
  
  // 定义 actions
  function increment() {
    count.value++
  }
  function decrement() {
    count.value--
  }

  // 千万不要忘记这里要 return
  return { count, double, increment, decrement }
})

对比发现组合式 Store 的用法与 Vue 组件的  setup 用法是一致的,咱们项目中会采用这种用法来开发。

1.3 State

State 实际上就是用来共享访问的数据,这些数据会涉及到访问、变更等操作,我们分别来学习 State 的相关操作。

  1. 访问,State 的数据是使用 reactive 创建的,直接通过 Store 实例属性的方式即可访问,这种访问方式也包括了 getters 的访问。
<!-- pages/test/index.vue -->
<script setup>
  // 导入定义好的 Store
  import { useCounterStore } from '@/stores/counter.js'
  // 创建 Store 实例
  const counterStore = useCounterStore()
  // 像普通过 reactive 包装数据一样来访问
  console.log(counterStore.count)
  // getters 可以可采用相同的方式来访问
  console.log(counterStore.double)
</script>

但是这里一定要注意是 reactive 的数据不允许解构,解构后的数据将会失去响应式,为了解决这个问题可以使用 Pinia 提供的工具函数 storeToRefs

   2.变更

变更 State 的数据有两种方式,一种是直接赋值,另一种是调用 $patch 方法。

<!-- pages/test/index.vue -->
<script setup>
  import { storeToRefs } from 'pinia'
  
  // 导入定义好的 Store
  import { useCounterStore } from '@/stores/counter.js'
  // 创建 Store 实例
  const counterStore = useCounterStore()

  let _count = 0
  // 更新 state
  function increment() {
    // 直接等号赋值
    // counterStore.count++
    // 调用 $patch 方法
    counterStore.$patch({
      count: ++_count,
    })
  }
  // 更新 state
  function decrement() {
    // 直接等号赋值
    // counterStore.count--
    // 调用 $patch 方法
    counterStore.$patch({
      count: --_count,
    })
  }
</script>
<template>
  <view class="content">
    <!-- 省略前面小节部分代码... -->
    <view class="counter">
      <button @click="decrement" class="button" type="primary">-</button>
      <input class="input" :value="counterStore.count" type="text" />
      <button @click="increment" class="button" type="primary">+</button>
    </view>
    <view class="state">
      <text class="text">count: {{ count }}</text>
      <text class="text">double: {{ double }}</text>
    </view>
  </view>
</template>

这两种方式都可以用来对 State 数据进行修改,在一次性需要更新多个数据时推荐使用  $patch 方法,单个数据更新时使用等号直接赋值。

1.4 持久化

Pinia 的数据是以全局的方式存储在内存中的,这会导致页面被刷新后数据丢失或重置,但实际开发中有的数据需要长时间的存储,即所谓的持久化,通常都是存入本地存储当中来实现的,在 Pinia 中通过插件来扩展持久化的功能。

  1. 安装
# 也可以使用其它包管理工具,如 yarn pnpm
npm i pinia-plugin-persistedstate

      2.将插件添加 Pinia 实例上

import { createSSRApp } from 'vue'
// 导入 Pinia
import { createPinia } from 'pinia'
// Pinia 持久化插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

import App from './App'
import '@/utils/utils'

export function createApp() {
  const app = createSSRApp(App)
  // 创建 Pinia 实例
  const pinia = createPinia()
  // 应用 Pinia 插件
  pinia.use(piniaPluginPersistedstate)

  app.use(pinia)

  return {
    app,
  }
}

      3.将数据持久化存储,为 defineStore 传入第3个参数,第3个参数是对象类型 

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// 2. 组合式 Store
export const useCounterStore = defineStore(
  'counter',
  () => {
    // 定义 state
    const count = ref(0)
    // 定义 getters
    const double = computed(() => count.value * 2)
    // 定义 actions
    function increment() {
      count.value++
    }
    function decrement() {
      count.value--
    }

    // 千万不要忘记这里要 return
    return { count, double, increment, decrement }
  },
  { persist: true }
)

count 数据发生改变后就会将数据存入本地存储当中了,但是这种方式有个弊端就是会将所有  State 数据持久化存储,这样会造成不必要的性能损耗,要解决这个问题也非常方便,通过 paths 来指定需要持久化存储的数据: 

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// 2. 组合式 Store
export const useCounterStore = defineStore(
  'counter',
  () => {
    // 定义 state
    const count = ref(0)
    // 定义 getters
    const double = computed(() => count.value * 2)
    // 定义 actions
    function increment() {
      count.value++
    }
    function decrement() {
      count.value--
    }

    // 千万不要忘记这里要 return
    return { count, double, increment, decrement }
  },
  {
    persist: {
      paths: ['count'],
    },
  }
)
  1. 配置

以上的用法在一般的 Vue 项目中可以满足基本的开发需要了,但是在 uni-app 中时却需要做一些额外的配置,原因在于 uni-app 中本地存储使用的是 uni.setStorageSync 而插件中使用的是 localStorage.setItem,为此需要我们自定义配置本地址存储的方法。

使用 createPersistedState 进行全局性配置

// main.js
import { createSSRApp } from 'vue'
// 导入 Pinia
import { createPinia } from 'pinia'
// Pinia 持久化插件
import { createPersistedState } from 'pinia-plugin-persistedstate'
// import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

import App from './App'
import '@/utils/utils'

export function createApp() {
  const app = createSSRApp(App)
  // 创建 Pinia 实例
  const pinia = createPinia()
  
  // 应用 Pinia 插件
  // pinia.use(piniaPluginPersistedstate)
  pinia.use(
    // 自定义 Pinia 插件
    createPersistedState({
      // 自定义本地存储的逻辑
      storage: {
        setItem(key, value) {
          uni.setStorageSync(key, value)
        },
        getItem(key) {
          return uni.getStorageSync(key)
        },
      },
    })
  )

  app.use(pinia)
  
  return {
    app,
  }
}

createPersistedState 传的参数中 storage 是用来自定义持久化存储方法的,其中 setItemgetItem 是内置固定的名称,在进行本地存储时插件内部会自动调用这两个方法,进而调用 uni.setStorageSync 将数据存入本地。

另外存入本地数据的名称默认为 Store 的名称,这个名称也允许自定义,使用 key 来指定:

// main.js
import { createSSRApp } from 'vue'
// 导入 Pinia
import { createPinia } from 'pinia'
// Pinia 持久化插件
import { createPersistedState } from 'pinia-plugin-persistedstate'
// import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

import App from './App'
import '@/utils/utils'

export function createApp() {
  const app = createSSRApp(App)
  // 创建 Pinia 实例
  const pinia = createPinia()
  // 应用 Pinia 插件
  // pinia.use(piniaPluginPersistedstate)
  
  pinia.use(
    // 自定义 Pinia 插件
    createPersistedState({
      // 自定义本地存数据的名称
      key: (id) => `__persisted__${id}`,
      // 自定义本地存储的逻辑
      storage: {
        setItem(key, value) {
          uni.setStorageSync(key, value)
        },
        getItem(key) {
          return uni.getStorageSync(key)
        },
      },
    })
  )

  app.use(pinia)
  
  return {
    app,
  }
}

 

 

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值