Vue3 官方推荐状态管理库Pinia

介绍

Pinia 是 Vue 官方团队推荐代替Vuex的一款轻量级状态管理库,允许跨组件/页面共享状态
Pinia 旨在提供一种更简洁、更直观的方式来处理应用程序的状态。
Pinia 充分利用了 Vue 3 的 Composition API。

官网:
Pinia符合直觉的 Vue.js 状态管理库

Pinia的核心概念

  • store:是存储状态(共享数据)的地方。
    • 是一个保存状态和业务逻辑的实体。它承载着全局状态。
    • 每个组件都可以读取/写入。
    • 官方推荐使用 hooks 的命名方式,以 use 开头。例如:useCountStoreuseUserStoreuseCartStoreuseProductStore
  • state:是 store 中用于存储应用状态的部分。
    • 通俗来讲,state 是真正存储数据的地方,它就是存放在store里的数据。
    • 官方要求 state 写成函数形式,并且要return一个对象。
      示例:state() { return {} }
  • getters:从存储的状态中派生数据,类似于 Vue 中的计算属性(computed)。
    • 是一种依赖于 store 状态并产生计算值的函数。这些值将被缓存,直到依赖的状态改变。
  • actions:是用于改变状态的方法。

安装与配置 Pinia

  1. 通过npm或yarn安装Pinia:
npm install pinia
# 或者使用 yarn
yarn add pinia
  1. 在Vue应用文件中(通常是main.jsmain.ts),引入并使用Pinia:
// 引入 createApp 用于创建实例
import {
   
    createApp } from 'vue';
// 引入 App.vue 根组件
import App from './App.vue';
// 从 Pinia 库中引入 createPinia 函数,用于创建 Pinia 实例
import {
   
    createPinia } from 'pinia';

// 创建一个应用
const app = createApp(App)
// 创建 Pinia 实例
const pinia = createPinia();
// 将 Pinia 实例注册到 Vue 应用实例中,使得整个应用可以使用 Pinia 进行状态管理
app.use(pinia);

// 挂载整个应用到app容器中
app.mount('#app')

通过以上步骤,成功地在 Vue 项目中集成了 Pinia 状态管理库,为应用提供了集中式的状态管理功能,可以在组件中通过使用 Pinia 的 store 来管理和共享数据。

此时开发者工具中已经有了pinia选项:
在这里插入图片描述

Store

Store 是一个保存状态和业务逻辑的实体。它承载着全局状态。

定义Store

Pinia 使用 defineStore 定义Store。

import {
   
    defineStore } from 'pinia'
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useCountStore = defineStore('count', {
   
   
  // 其他配置...
})

defineStore()

  1. 第一个参数(store 的 ID)
    • 这是一个字符串,用于唯一标识一个 store。 defineStore('count', {})中的count就是这个store的ID。
    • 必须传入, Pinia 将用它来连接 store 和 devtools。
  2. 第二个参数(配置对象)
    • 可接受两类值:Setup 函数或 Option 对象。
    • 这个对象包含了 store 的各种配置选项,主要有以下几个重要属性:stateactionsgetters
Option Store

与 Vue 的选项式 API 类似,可以传入一个带有 stateactionsgetters 属性的 Option 对象。

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

可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

Setup Store

与 Vue 组合式 API 的 setup 函数 相似,可以传入一个函数,该函数定义了一些响应式属性和方法,并且 return 一个带有需要暴露出去的属性和方法的对象。

import {
   
    defineStore } from "pinia";
export const useCountStore = defineStore('count', () => {
   
   
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
   
   
    count.value++
  }
  
  // 把要在组件中使用到的属性、方法暴露出去
  return {
   
    count, doubleCount, increment }
})

在 Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

注意,要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性。这意味着,你不能在 store 中使用私有属性。不完整返回会影响 SSR ,开发工具和其他插件的正常运行。

使用 Store

虽然定义了一个 store,但在使用 <script setup> 调用 useStore()(或者使用 setup() 函数) 之前,store 实例是不会被创建的:
在这里插入图片描述
Pinia中,没有名为 count 的store。

调用 useStore()后,Pinia 自动将store安装到vue应用中:

<script setup lang="ts">
import {
     
      useCountStore } from '@/store/count';

// 调用useCountStore函数得到一个countStore实例
// 一旦 store 被实例化,可以直接访问在 store 的 state、getters 和 actions 中定义的任何属性。
// 调用useCountStore后,Pinia 自动将store安装到vue应用中
const countStore = useCountStore()

console.log(countStore)         // 一个reactive对象
console.log(countStore.count)   // 0
</script>

通过工具vue devtools查看Pinia,名为count的store已经被安装到vue应用中:
在这里插入图片描述
通过工具vue devtools查看Count.vue:
在这里插入图片描述
Count.vue组件里:
countStore 是一个 reactive 定义的响应式对象。
sum是一个Ref(响应式引用)类型的数据。

通过实例countStore访问statecount属性:

// 直接访问, 不需要使用.value
const count1 = countStore.count;
// 通过访问 store 实例的 $state 属性来获取状态值
const count2 = constStore.$state.count

// 解构 constStore 
const {
   
    count } = constStore

每个 store 都被 reactive 包装过,所以可以自动解包任何它所包含的 Ref(ref()computed()…)。

  • 在 Vue 3 中,如果一个 reactive 对象包含了 ref 类型的数据,直接访问这个 ref 数据时不需要使用 .value
    • 这是因为 Vue 的响应式系统会自动处理这种嵌套的情况。当访问 reactive 对象中的 ref 数据时,Vue 会自动解包 ref 的值,就可以直接获取到 ref 所包裹的值,而无需显式地使用 .value
  • 当从 store 中解构状态时,如果直接解构赋值给变量,这些变量会失去响应性。
    • 直接解构出来的 count 属性失去响应性,值始终为 0。不会随着 store 中的状态变化而自动更新。

从 Store 解构

使用 storeToRefs() 解构store,解构后的属性保持响应性。它将为每一个响应式属性创建引用。

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

执行console.log(storeToRefs(countStore)),看看控制台打印结果:
storeToRefs()只关注store里的数据,不关注store里的方法,不会对方法进行ref包装。
解构出来的属性都是放在stategetter 里面的数据。
在这里插入图片描述

为什么不使用toRefs()解构store呢?
toRefs()也可以解构store,但是它会把store的全部属性(数据、方法)变成ref类型。
执行console.log(toRefs(countStore)),看看控制台打印结果:
在这里插入图片描述

State

在大多数情况下,state 都 store 的核心。
在 Pinia 中,state 被定义为一个返回初始状态的函数。

import {
   
    defineStore } from 'pinia'

const useStore = defineStore('storeId', {
   
   
  // 为了完整类型推理,推荐使用箭头函数
  state: () => {
   
   
    return {
   
   
      // 所有这些属性都将自动推断出它们的类型
      count: 0,
      name: 'Eduardo',
      isAdmin: true,
      items: [],
      hasChanged: true,
    }
  },
})

如果在tsconfig.json中启用了 strict,或者启用了 noImplicitThis,Pinia 将自动推断变量的状态类型。

在某些情况下,需要使用类型断言:

const useStore = defineStore('storeId', {
   
   
  state: () => {
   
   
    return {
   
   
      // 用于初始化空列表
      userList: [] as UserInfo[],
      // 用于尚未加载的数据
      user: null as UserInfo | null,
    }
  },
})

interface UserInfo {
   
   
  name: string
  age: number
}
  • userList: [] as UserInfo[]

    • userList: []:这部分将userList初始化为一个空数组。在应用启动时,这个属性没有任何值,所以初始化为一个空数组可以确保有一个明确的初始状态。
    • as UserInfo[]:类型断言,明确指定userList的类型为UserInfo[],即一个由UserInfo类型元素组成的数组。
    • 在使用userList时,TypeScript 可以进行类型检查,确保只向数组中添加符合UserInfo类型的元素,减少类型错误的发生。
  • user: null as UserInfo | null

    • user: null:将user初始化为null。这表示在应用启动时,还没有特定的用户信息被加载或设置,所以初始值为null
    • as UserInfo | null:类型断言,明确指定user的类型为UserInfo | null。这意味着user可以是一个符合UserInfo类型的对象,也可以是null
    • TypeScript 可以在编译时进行类型检查,确保对user的操作符合其类型定义。例如,如果尝试将一个不兼容的类型赋值给user,TypeScript 会报错,从而避免运行时错误。

可以用一个接口定义 state,并添加 state() 的返回值的类型:

interface State {
   
   
  userList: UserInfo[]
  user: UserInfo | null
}

const useStore = defineStore('storeId', {
   
   
  state: (): State => {
   
   
    return {
   
   
      userList: [],
      user: null,
    }
  },
})

interface UserInfo<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值