Vue3中的Pinia

发布时间 2023-12-01 14:18:52作者: 自学Java笔记本

什么是Pinia

官方文档:https://pinia.vuejs.org/zh/introduction.html

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的 export const state = reactive({}) 来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:

Devtools 支持

  • 追踪 actions、mutations 的时间线
  • 在组件中展示它们所用到的 Store
  • 让调试更容易的 Time travel
  • 热更新
  • 不必重载页面即可修改 Store
  • 开发时可保持当前的 State

插件:可通过插件扩展 Pinia 功能
为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。支持服务端渲染

基础示例

// stores/counter.js
import { defineStore } from 'pinia'
// useCounterStore 就相当于vuex 中store
// 第一个参数是你的应用中 Store 的唯一 ID
export const useCounterStore = defineStore('counter', () => {
  // count 表示的就是state中的数据
  const count = ref(0)
  // increment()方法可以看作是actions,同时支持异步的直接修改state中的数据,刨除了mutations
  function increment() {
    count.value++
  }

  return { count, increment }
})

然后你就可以在一个组件中使用该 store 了:

<script setup>
import { useCounterStore } from '@/stores/counter'
// useCounterStore是向外暴露的一个store,而counter是唯一id
const counter = useCounterStore()
counter.count++
// 自动补全! ✨
counter.$patch({ count: counter.count + 1 })
// 或使用 action 代替
counter.increment()
</script>
<template>
  <!-- 直接从 store 中访问 state -->
  <div>Current Count: {{ counter.count }}</div>
</template>

那么对于vuex中之间留下的辅助函数依旧可以使用

// defineComponent 为 vue3中的新语法,就是一个计算属性
export default defineComponent({
  computed: {
    // 其他计算属性
    // ...
    // 允许访问 this.counterStore 和 this.userStore
    ...mapStores(useCounterStore, useUserStore)
    // 允许读取 this.count 和 this.double
    ...mapState(useCounterStore, ['count', 'double']),
  },
  methods: {
    // 允许读取 this.increment()
    ...mapActions(useCounterStore, ['increment']),
  },
})

pinia中,去除了 modules的概念,每一个store都是一个独立的模块,并且支持组合式风格的API

安装:npm install pinia

案例演示

image

演示效果:根组件渲染Number,Son1组件 Son2组件可以修改Number使得全局都是响应式变化

步骤:

  • 安装Pinia npm install pinia
  • 在main.js中注册
import { createApp } from 'vue'
// 导入 pinia
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia() // 创建Pinia实例
app.use(pinia) // 将Pinia 挂载到App上
app.mount('#app')
  • 创建一个store/xxxxx.js表示为一个store
// 引入pinia
import { defineStore  } from 'pinia'
import {ref} from 'vue'

// 定义一个 store
// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,
//同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useCountStore = defineStore('countStore',()=>{
    // 顶一个响应式变量
    const count = ref(0)

    function increment() {
      count.value++
    }

    function decrement() {
      count.value--
    }
    // 向外提供 这三个属性方法
    return { count,increment , decrement}
}) 
  • 在根组件使用
<template>
  <div> 
    <h3>APP.vue 根组件   -{{ countStore.count }}</h3>
    <Son1Com></Son1Com>
    <Son2Com></Son2Com>
  </div>
</template>

<script setup>
import Son1Com from './components/Son1Com.vue'
import Son2Com from './components/Son2Com.vue'
// 引入
import { useCountStore } from './stores/counter'
// 获取到 useCountStore 的一个对象
const countStore = useCountStore()
</script>
  • 在子组件Son1 和Son2中使用
<template>
  <div>
    我是Son1 - {{ countStore.count }} - <button @click="countStore.increment()">+</button>
  </div>
</template>

<script setup>
import { useCountStore } from '../stores/counter'
const countStore = useCountStore()
</script>

定义Store

官网:https://pinia.vuejs.org/zh/core-concepts/
在Store中支持选项式和组合式,为了迎合vue3,这里采用组合式定义,选项式与vuex中定义几乎一致
在Store中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions
import {ref ,computed} from 'vue'
import { defineStore  } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }
 // 声明一个计算属性
   const computedCount = computed(()=>{return count.value * 20})
  return { count, increment , computedCount }
})

为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:

<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>

Action异步实现

action 也可通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全✨)。不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action!

案例引入:
编写方式:异步action函数的写法和组件中获取异步数据的写法完全一致
接口地址:http://geek.itheima.net/v1_0/channels
需求:在Pinia中获取频道列表数据,并把数据渲染APP组件的模板中

步骤:
安装axios,同时封装axios

// request.js
import axios from 'axios';

const request = axios.create({
  // 在这里可以添加自定义的配置
  baseURL: 'http://geek.itheima.net/v1_0/', // 你的 API 地址
  timeout: 5000, // 请求超时时间(毫秒)
});

// 添加拦截器等其他配置
// 添加请求拦截器
request.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么 (默认axios 会多包装一层data,需要响应拦截器中处理一下)
  return response.data
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  return Promise.reject(error)
})

export default request;

定义api接口

import request from "../utils/request";

// 获取频道列表
export const getChannels = () => {
  return request.get('/channels')
}

定义一个store/channle.js 用于声明 pinia

import { defineStore  } from 'pinia'
import {ref} from 'vue'
import {getChannels} from '../api/channel'
export const useChannelStore = defineStore('channel',()=>{
  // 声明 频道列表数据
  const channels = ref([])
  // 声明操作 频道列表的方法
  const getChannelsList = async () => {
    // 发起请求
    const res = await getChannels()
    channels.value = res.data.channels
  }
  return {channels ,getChannelsList}
})

在页面中调用:

<script setup>
// 引入
import {useChannelStore} from './stores/channel'
// 获取pinia
const channelStore = useChannelStore()
// 调用发起请求,getChannelsList方法调用后,会将数据封装到channels中,页面直接渲染即可
channelStore.getChannelsList()
</script>

<template>
  <div>
    <ul>
      <li v-for="item in channelStore.channels" :key="item">
        {{ item }}
      </li>
    </ul>
  </div>
</template>

Pinia - 持久化

从上诉的案例中,都没有持久化,只要页面刷新了,所对应的store都被重置了,在实际开发场景中是不允许的,
我们要配合本地去实现pinia的持久化
以下是基本原生的持久化,又更好的策略后面再说

  • 定义要给store.js 用于本地化
// utils/stores.js
export const setChannels = (obj) => {
  // 将数据存入本地
    localStorage.setItem('channels', JSON.stringify(obj))
}

export const getChannelsObj = () => {
  // 从本地获取数据,如果为空,返回一个默认值数组
  const obj =  JSON.parse(localStorage.getItem('channels'))
  const defaultList = []
    return obj?obj:defaultList
}
  • 声明一个store.js 文件定义pinia
import { defineStore  } from 'pinia'
import {ref} from 'vue'
import {getChannels} from '../api/channel'
import {setChannels , getChannelsObj} from '../utils/stores'
export const useChannelStore = defineStore('channel',()=>{
  // 声明 频道列表数据
  const channels = ref(getChannelsObj()) // 从本地获取数据
  // 声明操作 频道列表的方法
  const getChannelsList = async () => {
    // 发起请求
    const res = await getChannels()
    channels.value = res.data.channels
    setChannels(res.data.channels)  // 将值存入到本地
  }
  return {channels ,getChannelsList}
})

这样就持久化了

Pinia持久化插件

官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/

  • 安装
    npm i pinia-plugin-persistedstate

  • 将插件添加到 pinia 实例上,再mian.js文件中

import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
  • 创建 Store 时,将 persist 选项设置为 true。
import { defineStore } from 'pinia'

export const useStore = defineStore(
  'main',
  () => {
    const someState = ref('你好 pinia')
    return { someState }
  },
  {
    persist: true, // 表示持久化
  },
)

更多的配置项:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/config.html#paths

该插件的默认配置如下:

使用 localStorage 进行存储
store.$id 作为 storage 默认的 key
使用 JSON.stringify/JSON.parse 进行序列化/反序列化
整个 state 默认将被持久化
如何你不想使用默认的配置,那么你可以将一个对象传递给 Store 的 persist 属性来配置持久化。

如果想自定义名称:

import { defineStore } from 'pinia'

export const useStore = defineStore('store', {
  state: () => ({
    someState: '你好 pinia',
  }),
  persist: {
    key: 'my-custom-key',//这个 Store 将被持久化存储在 localStorage 中的 my-custom-key key 中。
  },
})

image

总结:
Pinia 是 用来做什么的?

  • 新一代的状态管理工具,代替vuex

Pinia中还需要mutaiton吗?

  • 不需要,actions即支持同步也支持异步

Pinia 如何实现getter?

  • 基于computed计算属性函数
    const getChannels = computed(()=>channels.value)

Pinia产生的Store如何解构赋值数据保持响应式?

  • 通过 storeToRefs()

Pinia 如何快速实现持久化

  • 使用插件即可