首页 > 其他分享 >Vue3 官方推荐状态管理库Pinia

Vue3 官方推荐状态管理库Pinia

时间:2024-09-03 20:53:38浏览次数:12  
标签:count const countStore state 官方 Pinia Vue3 store

介绍

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 {
  name: string
  age: number
}

在组件中访问State

  1. 默认情况下,通过 store 实例访问 state,直接对其进行读写。
<script setup lang="ts">
import { useCountStore } from '@/store/count';
// 一旦 store 被实例化,可以直接访问在 store 的 state、getters 和 actions 中定义的任何属性。
const countStore = useCountStore()
countStore.count ++ 
</script>
  1. 在 Vue3 的选项式API中,可以使用 mapState() 辅助函数将 state 属性映射为只读的计算属性
<script>
import { mapState } from 'pinia';
import { useCountStore } from '@/store/count';
export default {
  computed: {
   // 使用数组形式
   // 可以访问组件中的 this.count
   // 与从 store.count 中读取的数据相同
   ...mapState(useCountStore, ['count']),
   // 通过对象形式传入映射配置
   ...mapState(useCountStore, {
     // 给属性 count 取别名为 myOwnCount
     myOwnCount: 'count',
     // 定义了一个名为 double 的计算属性,是一个函数,接受 store 作为参数
     doubleCount: store => store.count * 2,
     // 它可以访问 `this`,但它没有标注类型...
     magicValue(store) {
       return store.someGetter + this.count + this.doubleCount
     },
   }),
  },
  methods: {
    incrementCount() {
      this.$store.dispatch('count/increment');
    },
  },
};
</script>

在这个示例中:

  • import { mapState } from 'pinia';:从 Pinia 库中引入 mapState 辅助函数。这个函数用于将 Pinia store 的状态映射为 Vue 组件的计算属性,使得在组件中可以方便地访问 store 的状态。
  • 通过mapState(useCountStore, ['count'])将 Pinia store 中的 count 状态映射为组件的计算属性。这样在组件中可以直接使用 count 来访问 store 中的状态,并且这个属性是只读的。
  • methods中定义了一个incrementCount方法,通过this.$store.dispatch('count/increment')来调用 store 中的increment action,实现对状态的修改。
  • myOwnCount: 'count':将 store 中的 count 状态映射为组件的计算属性 myOwnCount,这样可以使用 this.myOwnCount 来访问与 store.count 相同的值,但使用了自定义的属性名。
  • magicValue(store) { return store.someGetter + this.count + this.doubleCount}:定义了一个名为 magicValue 的计算属性,它是一个函数,接受 store 作为参数。
    • 在这个函数中,可以访问 store.someGetter(假设 store 中有这个 getter)、组件中的 this.countthis.doubleCount,并返回它们的组合结果。
    • 注意,这里的 magicValue 函数没有明确的类型标注,可能会在某些情况下导致类型不明确的问题。
  1. 在 Vue3 的选项式API中,可以使用 mapWritableState() 辅助函数将 state 属性映射为可写的计算属性,可以在组件中直接读取/修改 store 的状态。
<script>
import { mapWritableState } from 'pinia';
import { useCountStore } from '@/store/count';
export default {
  computed: {
   // 使用数组形式
   // 可以访问组件中的 this.count,并允许设置它。
   // 例如:直接操作 this.count++ 修改count的值
   // 与从 store.count 中读取的数据相同
   ...mapWritableState(useCountStore, ['count']),
   // 通过对象形式传入映射配置
   ...mapWritableState (useCountStore, {
     // 给属性 count 取别名为 myOwnCount
     myOwnCount: 'count',
   }),
  },
  methods: {
    incrementCount() {
      this.count++;
    },
  },
};
</script>

mapState() 的区别

  • mapWritableState() 不能像 mapState() 那样传递一个函数来进行复杂的状态映射或计算。
  • mapState() 中,可以通过传递一个函数来实现更灵活的状态映射,例如根据 store 的多个状态属性计算出一个新的属性值。
  1. 使用storeToRefs函数,确保解构出来的 state 属性保持响应性。

注意
新的属性如果没有在 state() 中被定义,则不能被添加。它必须包含初始状态。
例如:如果 secondCount 没有在 state() 中定义,无法执行 useCountStore .secondCount= 2

  • 明确的初始状态:通过要求所有可访问的属性在 state() 中定义并包含初始状态,Pinia 确保了应用在任何时候都有一个明确的、已知的状态起点。
  • 可控的状态变更:只允许修改在 state() 中定义的属性,可以防止意外地引入新的状态变量,从而降低了由于错误的状态修改而导致的错误风险。
  • 正确的响应式更新:Pinia 的响应式系统依赖于对已知状态属性的跟踪。只有在 state() 中定义的属性才能被 Pinia 的响应式系统正确地跟踪和更新,确保了状态变化能够及时反映在界面上。

修改State

  1. 直接修改
<script setup lang="ts">
import { useCountStore } from '@/store/count';
const countStore = useCountStore()
countStore.count ++ 
</script>
  1. 使用$patch$patch是一个用于批量更新 store 状态的方法。
    $patch接受一个对象作为参数,该对象的属性将被用来更新 store 的状态。
<script setup lang="ts">
import { useCountStore } from '@/store/count';
const countStore = useCountStore()
countStore.$patch({ count: 5 })
</script>

使用$patch方法将 store 中的count状态更新为5

$patch 的优点是批量更新,可以一次性更新多个状态属性,而不需要分别调用多个 actions 或直接修改状态属性。

countStore.$patch({ 
  count: 5,
  name: 'John',
  value: 'new Value!'
})

同时更新了countnamevalue这3个状态属性。

替换 state

在 Pinia 中,直接完全替换 store 的 state 会破坏其响应性。
因为 Pinia 的响应式系统是基于对特定状态对象的跟踪和变化检测来实现的。如果完全替换了这个对象,响应式系统将无法正确地检测到变化,从而导致相关的组件不能自动更新。

<script setup lang="ts">
import { useCountStore } from '@/store/count';
const countStore = useCountStore()
// 直接替换 state 对象
countStore.$state = { count: 10 }
</script>

在这个例子中,直接将整个 state 对象替换为一个新的对象,这样会导致组件中使用 countStore.count 的地方不会自动更新,因为响应式系统无法检测到这种替换操作。

不管是Vue2 还是 Vue3,直接替换 state 都会破坏响应性。

可以使用 $patch 方法:利用$patch 方法批量更新的特性,可以全部更新store 的 state,也可以只进行部分更新,而不会破坏响应性。它接受一个对象或一个函数作为参数,用于描述要进行的更新操作。

// 接受一个对象
countStore.$patch({ count: 5 });
// 或者使用函数形式
countStore.$patch(state => {
  state.count = state.count + 1;
});
  1. 变更 pinia 实例的 state 来设置整个应用的初始 state
import { createPinia } from 'pinia';
const pinia = createPinia();

// 直接修改pinia.state.value来设置应用的初始状态
pinia.state.value = {
  someStoreKey: {
    someProperty: initialValue,
  },
};

重置State

  1. 如果Pinia Store 是使用 Option Store(选项式 API) ,调用 store 的 $reset() 方法将 state 重置为初始值。
<script setup lang="ts">
import { useCountStore } from '@/store/count';
const countStore = useCountStore()
// 打印countStore,可以看到$reset 属性
console.log(countStore)

const resetCountStore = () => {
  countStore.$reset()
}
</script>

$reset() 内部,会调用 state() 函数来创建一个新的状态对象,并用它替换当前状态。

  1. 如果Pinia Store 是使用Setup Stores ,需要创建 store 自己的 $reset() 方法:
export const useCountStore = defineStore('count', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }
  function $reset() {
    count.value = 0
  }
  
  // 把要在组件中使用到的属性、方法暴露出去
  return { count, doubleCount, increment }
})

如果 state 的属性 既有使用ref()定义的,也有reactive()定义的,可以在 $reset() 使用 isRef()isReactive() 检查类型:

  • isRef()检查某个值是否为 ref。
  • isReactive()检查一个对象是否是由 reactive()shallowReactive() 创建的代理。

订阅 state

  1. 通过 store 的 $subscribe() 方法侦听 state 及其变化。
  • $subscribe接受一个回调函数作为参数,这个回调函数会在 store 的状态发生变化时被调用。
  • $subscribe方法的返回值是一个函数,用于取消订阅。
  • 使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次。
const countStore = useCountStore()
countStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  // mutation.type // 'direct' | 'patch object' | 'patch function'
  // 和 countStore.$id 一样
  // mutation.storeId // 'count'
  // 只有 mutation.type === 'patch object'的情况下才可用
  // mutation.payload // 传递给 countStore.$patch() 的补丁对象。
  
  console.log(mutation, state)
  if (mutation.type === 'direct') {
    console.log(`直接修改, mutation.type 是 'direct'`);
  } else if (mutation.type === 'patch object') {
    console.log(`使用对象形式的 $patch修改状态,mutation.type 是 'patch object'`);
    console.log('补丁对象:', mutation.payload);
  } else if (mutation.type === 'patch function') {
    console.log(`使用函数形式的 $patch修改状态,mutation.type 是 'patch function'`);
  }
})

$subscribe回调函数参数:

  • mutation:包含了关于状态变化的信息,比如变化的类型和路径。
    • mutation.type:以获取状态变化的类型。值有:
      • 'direct':直接修改状态
      • 'patch object':使用对象形式的 $patch 修改状态
      • 'patch function':使用函数形式的 $patch 修改状态
    • mutation.storeId:获取当前 store 的唯一标识符。比如示例中的 'count'
    • mutation.payload:仅在 mutation.type === 'patch object' 的情况下可用,它是传递给 countStore.$patch() 的补丁对象,包含了用于更新状态的属性和值。
  • state:当前的 store 状态。

例如,当使用以下方式修改 store 的状态时:

// 直接修改, mutation.type 是 'direct'
countStore.count++;

// 使用对象形式的 $patch修改状态,mutation.type 是 'patch object'
countStore.$patch({ count: countStore.count + 1 });

// 使用函数形式的 $patch修改状态,mutation.type 是 'patch function'
countStore.$patch(state => {
  state.count= 15;
});

$subscribe 回调函数会被触发,根据状态变化的类型记录相应的信息。
在这里插入图片描述


在 Pinia 中,如果在组件的 setup() 方法中使用 $subscribe 来订阅 store 的状态变化,默认情况下,这个订阅会被绑定到添加它们的组件上。只要组件存在,订阅就会一直有效。当该组件被卸载时,订阅也会自动被删除。

如果想在组件卸载后依旧保留状态订阅,可以将 { detached: true } 作为第二个参数传递给 $subscribe 方法,以将状态订阅从当前组件中分离:

<script setup>
const countStore = useCountStore()
// 此订阅器即便在组件卸载之后仍会被保留
const unsubscribe = countStore.$subscribe(callback, { detached: true })

// 手动取消订阅
unsubscribe()
</script>

当组件被卸载时,订阅不会被自动删除。可以在适当的时候调用 unsubscribe() 方法来手动取消订阅。

  1. 可以在组件中使用watch监视 state
watch(countStore.$state, (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

Getter

在 Pinia 中,Getter 用于从 store 的状态中派生新的数据或者对状态进行计算。
Getter 完全等同于 store 的 state 的计算值。
可以通过 defineStore() 中的 getters 属性来定义。

  1. getter使用箭头函数
import { defineStore } from "pinia";

export const useCountStore = defineStore('count', {
  state() {
    return {
      count: 0,
    }
  },
  getters: {
    // 推荐使用箭头函数,箭头函数没有this
    // state作为 getter 的第一个参数
    doubleCount: (state) => state.count * 2,
  }
})

在这个例子中,定义了一个名为 doubleCount 的 Getter,它返回 count 状态属性的两倍。

注意:getter 使用箭头函数,箭头函数没有thisthis的值是undefined

  1. getter使用常规函数
    如果要在getter 中使用this来访问 store 实例,那要使用使用常规函数定义 getter(在TypeScript中,必须明确getter的返回类型):
import { defineStore } from "pinia";
export const useCountStore = defineStore('count', {
  state() {
    return {
      count: 0,
    }
  },
  getters: {
    doubleCount():number {
      return this.count * 2
    }
  }
})

在组件中使用:

<template>
  <div>
    <p>Count: {{ countStore.count }}</p>
    <p>Double Count: {{ countStore.doubleCount }}</p>
  </div>
</template>

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

getters 具有缓存功能,只要它们依赖的状态没有发生变化,多次调用同一个 getter 将返回缓存的值,而不会重复执行计算逻辑。

访问其它getter

通常情况下,getter 主要依赖于 store 的 state 来派生新的数据。
有时候,getter 也可以使用其他 Getter 来进行更复杂的计算。

import { defineStore } from "pinia";
export const useCountStore = defineStore('count', {
  state() {
    return {
      count: 0,
    }
  },
  getters: {
    // 自动推断出返回类型是一个 number
    doubleCount(state) {
      return state.count * 2
    },
    // 自动推断出返回类型是一个 number
    tripleCount(state) {
      return state.count * 3
    },
    // 返回类型**必须**明确设置
    combinedCount(): number {
      // 整个 store 的 自动补全和类型标注 
      return this.doubleCount + this.tripleCount
    },
  }
})

在这个例子中,combinedCount getter 使用普通函数的形式定义,因此,在函数内部可以正确地使用 this 访问其他 getterstate 属性。
此时,this指向store实例 useCountStore

在组件中使用:

<template>
  <div>
    <p>Count: {{ countStore.count }}</p>
    <p>Double Count: {{ countStore.doubleCount }}</p>
    <p>Triple Count: {{ countStore.tripleCount }}</p>
    <p>Combined Count: {{ countStore.combinedCount }}</p>
  </div>
</template>

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

渲染结果:
在这里插入图片描述

向 getter 传递参数(让getter返回一个函数)

Getter 只是幕后的计算属性,所以不可以向它们传递任何参数。
可以从 getter 返回一个函数,该函数可以接受任意参数
当 getter 返回一个函数,getter 将不再被缓存。

import { defineStore } from "pinia";
export const useCountStore = defineStore('count', {
  state() {
    return {
      count: 0,
    }
  },
  getters: {
    doubleCount: (state) =>  state.count * 2,
    tripleCount: (state) =>  state.count * 3,
    getDoubleSum: (state) => {
      const getSum = function(flag: boolean) {
        if(flag) {
          const store = useCountStore();
          return (store.doubleCount + store.tripleCount) * 2;
        }
      }
      return getSum;
    }
  },
})

在组件中使用:

<template>
  <div>
    <p>Count: {{ countStore.count }}</p>
    <p>Double Count: {{ countStore.doubleCount }}</p>
    <p>Triple Count: {{ countStore.tripleCount }}</p>
    <p>Double Sum: {{ countStore.getDoubleSum(true) }}</p>
  </div>
</template>

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

访问其他 store 的 getter

想要使用另一个 store 的 getter 的话,那就直接在 getter 内使用就好:

import { useOtherStore } from '@/store/other-store'

export const useStore = defineStore('main', {
  state() {
    return {
      count: 0,
    }
  },
  getters: {
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.count + otherStore.data
    },
  },
})

在组件中访问getter

  1. 默认情况下,通过 store 实例访问 getter
  2. 在Vue3的选项式API中,可以使用 mapState() 辅助函数函数来将其映射为 getters:
<script>
import { mapState } from 'pinia';
import { useCountStore } from '@/store/count';
export default {
  computed: {
    // 使用数组形式
    // 允许在组件中访问 this.doubleCount
    // 与从 store.doubleCount 中读取的相同
    ...mapState(useCounterStore, ['doubleCount']),
    // 使用对象形式
    ...mapState(useCounterStore, {
      myDoubleCount: 'doubleCount',
      // 你也可以写一个函数来获得对 store 的访问权
      double: (store) => store.doubleCount,
    }),
  }
};
</script>

Action

Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择

import { defineStore } from "pinia";
export const useCountStore = defineStore('count', {
  state() {
    return {
      count: 0,
    }
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

action 可以通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全)

在组件中使用:

<template>
  <!-- 即使在模板中也可以 -->
  <button @click="countStore.increment()">Randomize</button>
</template>
<script setup>
import { useCountStore } from '@/store/count';
const countStore = useCountStore()
// 将 action 作为 store 的方法进行调用
countStore.increment()
</script>

Action 可以执行异步操作:

  • 使用 asyncawait:可以在 Action 的定义中使用 async 关键字来标记该函数为异步函数,然后在函数内部使用 await 来等待异步操作的完成。
actions: {
  async fetchDogImage() {
    try {
      const response = await fetch('https://dog.ceo/api/breeds/image/random');
      console.log('response', response)
      const data = await response.json();
      console.log(data)
      this.dogImage = data.message
    }catch (err) {
      console.log(err)
    }
  },
},

在组件中使用:

<template>
  <img v-if="countStore.dogImage" :src="countStore.dogImage" alt="">
</template>
<script setup>
import { useCountStore } from '@/store/count';
const countStore = useCountStore()
// 将 action 作为 store 的方法进行调用
countStore.fetchDogImage()
</script>
  • 返回 Promise:异步 Action 也可以返回一个 Promise,在调用 Action 的地方进行进一步的处理。
actions: {
  async fetchDogImage() {
     return await fetch('https://dog.ceo/api/breeds/image/random');
  },
},

访问其他 store 的 action

想要使用另一个 store 的话,直接在 action 中调用:

import { useOtherStore } from '@/store/other-store'
export const useStore = defineStore('main', {
  state() {
    return {
      count: 0,
    }
  },
  actions: {
    increment() {
      const otherStore = useOtherStore()
      this.count = this.count + otherStore.data
    }
  }
})

在组件中访问Action

  1. 默认情况下,将 action 作为 store 的方法进行调用
  2. 在Vue3的选项式API中,使用 mapActions() 辅助函数将 action 属性映射为组件中的方法:
import { mapActions } from 'pinia'
import { useCountStore } from '@/store/count';
export default {
  methods: {
    // 访问组件内的 this.increment()
    // 与从 store.increment() 调用相同
    ...mapActions(useCountStore, ['increment'])
    // 与上述相同,但将其注册为this.myOwnIncrement()
    ...mapActions(useCounterStore, { myOwnIncrement: 'increment' }),
  },
}

在组件中给 increment Action 取一个别名myOwnIncrement,可以通过this.myOwnIncrement()调用 increment Action。

订阅 action

通过someStore.$onAction(...)来为特定的 Pinia store(这里假设名为someStore)的 actions 进行监听传入的回调函数接收一个包含多个属性的对象,这些属性提供了关于正在执行的 action 的信息:

  • name:action 的名称。
  • store:当前的 store 实例,类似于外部的someStore
  • args:传递给 action 的参数数组。
  • after回调函数
    • 执行时机:在 action 的 promise 解决之后执行,即当 action 成功完成时。
    • 用途:允许在 action 解决后执行一个回调函数。例如,可以在这个回调函数中进行一些后续处理,如更新 UI、发送通知等。
  • onError回调函数
    • 执行时机:当 action 抛出错误或 promise 被 reject 时执行。
    • 用途:允许你在 action 出现错误时执行一个回调函数。例如,你可以在这个回调函数中记录错误日志、显示错误消息给用户、进行错误处理等。

someStore.$onAction(...)的返回值 是一个函数,用于取消订阅。
传递给 someStore.$onAction()的回调函数会在 action 本身之前执行。这意味着可以在 action 执行之前进行一些预处理或设置一些状态。

示例:

<template>
  <div>
    <p>Count: {{ countStore.count }}</p>
    <img v-if="countStore.dogImage" :src="countStore.dogImage" alt="">
    <button @click="countStore.increment">Increment</button>
    <button @click="countStore.fetchDogImage">换图</button>
  </div>
</template>

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

// 订阅 actions
const unsubscribe = countStore.$onAction(
  ({
    name, // action 名称
    store, // store 实例,类似 `someStore`
    args, // 传递给 action 的参数数组
    after, // 在 action 返回或解决后的钩子
    one rror, // action 抛出或拒绝的钩子
  }) => {
  
  // 这将在执行 store 的 action 之前触发。
  // 此时,已经调用 store 的action
  console.log(`Action "${name}" is triggered.`);
  
  // 这将在 action 成功并完全运行后触发。
  // 它等待着任何返回的 promise
  after((result) => {
    console.log(`Action "${name}" completed with result:`, result);
  });
  // 在 action 抛出或返回一个拒绝的 promise 时触发
  one rror((error) => {
    console.error(`Action "${name}" failed with error:`, error);
  });
});
countStore.fetchDogImage()


// 手动删除监听器
unsubscribe()
</script>

插件

由于有了底层 API 的支持,Pinia store 现在完全支持扩展。以下是Pinia store 支持扩展的内容:

  • 为 store 添加新的属性
  • 定义 store 时增加新的选项
  • 为 store 增加新的方法
  • 包装现有的方法
  • 改变甚至取消 action
  • 实现副作用,如本地存储
  • 仅应用插件于特定 store

Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。
Pinia 插件接收一个可选参数,即 context对象,这个对象包含了在 Pinia 应用中不同阶段的各种信息,允许插件在不同的上下文中进行操作和扩展。

  • context.pinia:这是使用createPinia()创建的 Pinia 实例。通过这个属性,插件可以访问 Pinia 的全局状态和方法,例如获取其他存储或应用插件的全局配置。
  • context.app(仅在 Vue 3 中可用):这是使用createApp()创建的当前应用实例。这个属性允许插件与 Vue 应用进行交互,例如访问应用的全局配置、注册全局组件或插件等。
  • context.store:这是当前插件想要扩展的存储实例。插件可以通过这个属性访问和修改存储的状态、动作(actions)和获取器(getters)等。
  • context.options:这是定义传给defineStore()的存储的可选对象。这个属性包含了存储的初始配置,可以在插件中进行修改或扩展,以改变存储的行为。

创建 Pinia 实例后,可以使用 pinia.use() 把插件添加到 Pinia 中。
在应用中创建的每个 store 都会应用这个插件。

创建一个用于日志记录的Pinia 插件:

export function myLoggingPlugin(context) {
  const { store } = context;

  const originalActions = store.$actions;

  Object.keys(originalActions).forEach((actionName) => {
    const originalAction = originalActions[actionName];

    store.$actions[actionName] = async function (...args) {
      console.log(`Before executing action "${actionName}" with args: ${args}`);
      const result = await originalAction.apply(this, args);
      console.log(`After executing action "${actionName}". Result: ${result}`);
      return result;
    };
  });
}

在这个示例中,插件在每个动作执行前后打印日志。

然后通过 pinia.use() 在 Pinia 中应用这个插件:

import { createPinia } from 'pinia';

const pinia = createPinia();
pinia.use(myLoggingPlugin);

只有在 Pinia 实例被应用后新创建的 store 才会应用Pinia插件。

扩展 Store

  1. 可以直接通过在一个插件中返回包含特定属性的对象来为每个 store 都添加上特定属性:
// 使用返回对象的方法, hello 能被 devtools 自动追踪到
pinia.use(() => ({ hello: 'world' }))

通过vue devtools 查看Pinia:
在名为countstore 中,hello属性已经自动被添加到store._customProperties中。devtools会自动追踪hello属性。
在这里插入图片描述

  1. 可以直接在 store 上设置属性,这种方式设置的属性不会被devtools自动追踪
pinia.use(({ store }) => {
  store.hello = 'world'
})

通过vue devtools 查看Pinia:
hello属性没有被添加到store._customProperties中,不会被devtools自动追踪。
在这里插入图片描述

如果想在 devtools 中调试 hello 属性,为了使 devtools 能追踪到 hello,确保在 dev 模式下将其添加到 store._customProperties 中:

pinia.use(({ store }) => {
  store.hello = 'world'
  // 确保你的构建工具能处理这个问题,webpack 和 vite 在默认情况下应该能处理。
  if (process.env.NODE_ENV === 'development') {
    // 添加你在 store 中设置的键值
    store._customProperties.add('hello')
  }
})

通过vue devtools 查看Pinia:
hello属性已经被添加到store._customProperties中。
在这里插入图片描述如果process报错:找不到名称“process”。这个错误提示表明在 TypeScript 项目中,缺少对 process 对象的类型定义。
解决方案:在tsconfig.json"compilerOptions" 属性中配置 "types": ["node"]

{
  "compilerOptions": {
    //...其他配置
    "types": ["node"]
  }
}

插件的更多功能请查看Pinia官网的插件

标签:count,const,countStore,state,官方,Pinia,Vue3,store
From: https://blog.csdn.net/fishmemory7sec/article/details/141726929

相关文章

  • vue3中交互反馈的用法及各种弹窗输入框最详细教学
    1.showToast1.1参数说明以上是showToast中的常用参数,接下来会对一部分进行演示1.1.1title现在添加了一个showToast方法,并且在里面写了title参数当我第一次进入这个页面时,会触发该弹窗,并且上面会有添加的文字《操作失败》上面是✓的原因是因为默认值为✓1.1.2......
  • zdppy+vue3+onlyoffice文档管理系统实战 20240902 上课笔记 登录功能优化
    遗留问题1、登录以后跳转最近文档2、如果用户没有登录应该自动跳转登录页面3、如果用户的token校验失败,应该自动调整登录界面4、按回车键自动跳转登录页面登录以后跳转最近文档constrouter=useRouter()router.push("/")实际代码:constloginData=awaitapi.login......
  • FastAPI+Vue3零基础开发ERP系统项目实战课 20240831上课笔记 查询参数和分页实现
    回顾获取路径参数什么是路径参数?/user/{id}什么时候使用?需要传递参数怎么实现类型转换?声明参数的类型怎么捕获文件路径?{file_path:path}什么是查询参数查询字符串是键值对的集合,这些键值对位于URL的?之后,以&分隔。http://127.0.0.1:8000/items/?skip=0&limit=10......
  • vue3整合antv x6实现图编辑器快速入门
    安装:npminstall@antv/x6--save如果使用umd包,可以使用下面三个CDN中的任何一个,默认使用X6的最新版:https://unpkg.com/@antv/x6/dist/index.jshttps://cdn.jsdelivr.net/npm/@antv/x6/dist/index.jshttps://cdnjs.cloudflare.com/ajax/libs/antv-x6/2.0.0/index.......
  • vue3使用路由守卫出现的问题合集
    登录后停留在登录页面无法跳转--24.9.3原代码:functionisRoute(to){letres=router.getRoutes()letresFil=res.filter(item=>item.path===to.path)returnresFil.length>0}router.beforeEach((to,from,next)=>{if(to.path!=='/login'......
  • Vue3+SpringBoot前端项目实战智慧实验室管理平台
    Vue3+SpringBoot前端项目实战:‌智慧实验室管理平台在当今数字化快速发展的时代,‌智慧实验室管理平台成为提升科研效率、‌优化资源配置的重要工具。‌本文将介绍如何使用Vue3和SpringBoot构建一个功能全面的智慧实验室管理平台,‌涵盖平台的设计思路、‌技术选型、‌核心功......
  • 前端Vue3项目VUE3+TypeScript企业级前端Vue项目
    前端Vue3项目VUE3+TypeScript企业级前端Vue项目‌Vue3+SpringBoot前端项目实战:‌智慧实验室管理平台‌在当今数字化快速发展的时代,‌智慧实验室管理平台的建设成为了提升科研效率与管理水平的关键一环。‌本文将通过一个实战案例,‌详细介绍如何使用Vue3和SpringBoot技术栈构建......
  • Vue3源码解析,打造自己的Vue3框架,实现技术深度与思维
    Vue3源码解析与打造自己的Vue3框架:‌技术深度与思维实践引言Vue.js作为当前最流行的前端框架之一,‌其不断迭代和更新推动了前端技术的快速发展。‌Vue3作为Vue.js的下一代主要版本,‌在性能、‌可维护性和开发体验方面进行了重大改进。‌本文将通过对Vue3源码的深入解析,‌探讨......
  • Vue3+Vite+Vant-UI+Pinia+VueUse开发双端业务驱动技术栈商用项目
    前言:个人git仓库,全是干货一、本次搭建项目涉及到vue3、vite、pinia、vue-router、typescript、element-plus,下面先简单介绍一下大家比较陌生的框架或库1、vue3vue团队官宣:2022年2月7日,vue3作为vue的默认版本。现在打开vue官网,界面默认显示的是vue3版本的指导文档。vue团队在......
  • Vite2.0+ElementPlus+Koa2+Mongo全栈开发通用后台系统Vue3
    Vite2.0+ElementPlus+Koa2+Mongo全栈开发通用后台系统Vue3前言当前基于NodeJs框架的全栈工程实践非常之火,作为一个很长时间未接触代码的前程序猿。一直有点手痒痒,想尝试一下这种全新的编程体验,于是就重新开始了填坑的不归之路。这一套框架是基于现在的前后台分离的指导原则来......