Skip to content

Pinia 状态管理

官网: https://pinia.vuejs.org/zh/

PiniaVue3官方推荐的状态管理库, 可以看作是 Vuex 的替代品

Pinia提供了更简单、更直观的API和更好的TypeScript支持

核心特点

  1. 更简单的API: 相比Vuex更简洁直观
  2. 组合式API友好: 完美支持Vue 3的组合式API
  3. TypeScript支持: 提供完整的类型推断
  4. 模块化设计: 不需要嵌套模块, 每个store都是独立的
  5. 轻量级: 体积小, 压缩后约1KB
  6. DevTools支持: 集成Vue DevTools

基本配置

安装

bash
# 安装 pinia
npm install pinia

配置

  • 修改src/main.ts
ts
import { createApp } from 'vue'
import App from './App.vue'

// 引入 createPinia, 用于创建 pinia
import { createPinia } from 'pinia'
// 创建 pinia
const pinia = createPinia()
// 挂载使用
createApp(App).use(pinia).mount('#app')

基本使用

Store是一个保存状态业务逻辑的实体, 每个组件都可以读取写入

Store有三个概念

  1. state: 相当于组件中的data
  2. getter: 相当于组件中的computed
  3. action: 相当于组件中的methods

src目录下新建store文件夹用于存放状态管理文件

  • 实现效果

Pinia Example

存储和读取数据

  • src/store/count.ts用于Count.vue组件
ts
// 引入 defineStore 用于创建 Store
import { defineStore } from 'pinia'

// 定义并暴露一个 Store
export const useCountStore = defineStore('count', {
  // 动作
  actions: {},
  // 状态
  state() {
    return {
      sum: 6,
      school: '坐忘道'
    }
  },
  // 计算
  getters: {}
})
  • src/store/talk.ts用于Talk.vue组件
ts
// 引入 defineStore 用于创建 Store
import { defineStore } from 'pinia'

// 定义并暴露一个 Store
export const useTalkStore = defineStore('talk', {
  // 动作
  actions: {},
  // 状态
  state() {
    return {
      talkList: [
        {id: 'yuysada01', content: '你今天有点怪,哪里怪?怪好看的!'},
        {id: 'yuysada02', content: '草莓、蓝莓、蔓越莓,你想我了没?'},
        {id: 'yuysada03', content: '心里给你留了一块地,我的死心塌地'}
      ]
    }
  },
  // 计算
  getters: {}
})

在组件中使用state中的数据

  • Count.vue组件
vue
<template>
  <h2>当前求和为: {{ countStore.sum }}</h2>
</template>

<script setup lang="ts" name="Count">
  // 引入对应的 useXxxxxStore
  import { useCountStore } from '@/store/count'
  
  // 调用 useXxxxxStore 得到对应的 store
  const countStore = useCountStore()
</script>
  • Talk.vue组件
vue
<template>
<ul>
    <li v-for="talk in talkStore.talkList" :key="talk.id">
      {{ talk.content }}
    </li>
  </ul>
</template>

<script setup lang="ts" name="Talk">
  import axios from 'axios'
  import { useTalkStore } from '@/store/talk'

  const talkStore = useTalkStore()
</script>

修改数据

修改数据有三种方式

1. 直接修改:

ts
import { useCountStore } from '@/store/sum'
  
const countStore = useSumStore()

// 直接修改
countStore.sum = 666

2. 批量修改: 可以同时修改多个

ts
import { useCountStore } from '@/store/sum'
  
const countStore = useSumStore()

// 批量修改
countStore.$patch({
  sum: 999,
  school: '家里蹲'
})

3. 借助 action 修改: action中可以编写一些业务逻辑

  • src/store/count.tsaction中编写业务逻辑
ts
import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  // ...
  actions: {
    // 加
    increment(value: number) {
      if (this.sum < 10) {
        // 操作 countStore 中的 sum
        this.sum += value
      }
    },

    // 减
    decrement(value: number){
      if(this.sum > 1){
        this.sum -= value
      }
    }
  },
  // ...
})
  • 组件中调用action
ts
import { useCountStore } from '@/store/sum'
  
const countStore = useSumStore()

// 调用对应 action
countStore.increment(1)

storeToRefs

借助storeToRefsstore中的数据转为ref对象, 方便在模板中使用

注意

pinia提供的storeToRefs只会将数据做转换, 而VuetoRefs会转换store中数据

  • 代码示例
vue
<template>
  <div class="count">
    <h2>当前求和为: {{ sum }}</h2>
  </div>
</template>

<script setup lang="ts" name="Count">
  import { useCountStore } from '@/store/count'
  // 从 pinia 中引入 storeToRefs
  import { storeToRefs } from 'pinia'

  // 得到 countStore
  const countStore = useCountStore()

  // 使用 storeToRefs 转换 countStore, 然后解构
  const { sum } = storeToRefs(countStore)
</script>

getters

state中的数据需要经过处理后再使用时, 可以使用getters配置

  • 追加getters配置
ts
// 引入 defineStore 用于创建 Store
import { defineStore } from 'pinia'

// 定义并暴露一个 Store
export const useCountStore = defineStore('count', {
  // 动作
  actions: {},
  // 状态
  state() {
    return {
      sum: 1,
      school: 'Miller'
    }
  },
  // 计算
  getters: {
    bigSum: (state) :number => state.sum * 10,
    upperSchool() :string {
      return this.school.toUpperCase()
    }
  }
})
  • 组件中读取数据
ts
import { useCountStore } from '@/store/sum'
  
const countStore = useSumStore()

// 获取 actions
const { increment, decrement } = countStore

// 获取 getters
let { sum, school, bigSum, upperSchool } = storeToRefs(countStore)

$subscribe

通过store$subscribe()方法侦听state及其变化

  • 代码示例
ts
import { useTalkStore } from '@/store/talk'

const talkStore = useTalkStore()

talkStore.$subscribe((mutate, state) => {
  console.log('LoveTalk', mutate, state)
  localStorage.setItem('talk', JSON.stringify(talkList.value))
})

store 组合式写法

  • 代码示例
ts
import { defineStore } from 'pinia'
import axios from 'axios'
import { nanoid } from 'nanoid'
import { reactive } from 'vue'

export const useTalkStore = defineStore('talk', () => {
  // talkList 就是 state
  const talkList = reactive(
    JSON.parse(localStorage.getItem('talkList') as string) || []
  )

  // getATalk 函数相当于 action
  async function getATalk() {
    // 发请求, 下面这行的写法是: 连续解构赋值 + 重命名
    let { data: { content: title } } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
    // 把请求回来的字符串,包装成一个对象
    let obj = { id: nanoid(), title }
    // 放到数组中
    talkList.unshift(obj)
  }
  return { talkList, getATalk }
})