Vue全家桶 - pinia 的理解和学习1(Pinia 核心概念的 Store、State、Getter、Action)

9 篇文章 0 订阅
2 篇文章 0 订阅

Pinia(Vue 的专属状态管理库)

Pinia 和 Vuex 的区别

设计理念和架构
Vuex 采用集中式架构,所有状态存储在一个全局状态树中,通过 mutationsactions 来修改和处理状态。
Pinia 采用去中心化的架构,每个模块有自己的状态,这使得 Pinia 在代码分割和模块化方面更加灵活。
TypeScript支持
Vuex 早期版本对 TypeScript 支持较弱,需要额外插件实现类型检查。
Pinia 从设计之初就考虑到了 TypeScript,提供了更好的类型推导和检查支持。‌
体积和性能
Pinia的体积较小,加载速度快,且因其轻量级设计,性能优于Vuex。这得益于Pinia充分利用了Vue 3的响应式系统优化。‌
使用难度
Pinia 的API设计更简洁,没有 Vuex 的 mutationsmodules 概念,这使得 Pinia 更容易理解和使用。Pinia 更适合初学者和快速开发项目,而 Vuex 则更适合复杂的项目和对状态管理有更高要求的开发者。‌
社区支持和生态系统
Vuex 作为 Vue.js 官方出品的状态管理库,拥有更强的社区支持和丰富的文档。而 Pinia 虽然是一个较新的库,但在 Vue 3 项目中因其轻量级和简洁性而受到青睐。
总结来说,选择Pinia还是Vuex取决于项目的具体需求和开发者的个人偏好。对于需要快速开发和维护简单性的项目,Pinia可能是更好的选择;而对于需要高度模块化和集成的大型复杂项目,Vuex可能更适合。

Vuex 的理解和学习 https://blog.csdn.net/weixin_54092687/article/details/140280584
Pinia 官方文档 https://pinia.vuejs.org/zh/
进一步学习Pinia的其他核心概念和服务器渲染 https://blog.csdn.net/weixin_54092687/article/details/140561225

定义 Store

// 第一步 -- src/store/store.ts 定义仓库
import { defineStore } from 'pinia'
export const useMyPiniaStore = defineStore('myPinia', { ... });

// 第二步 -- main.ts 注册 Pinia
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia();
createApp(App).use(pinia).mount('#app')

// 第三步 -- 组件中使用Store
<script setup>
import { useMyPiniaStore } from '@/store/store'
const store = useMyPiniaStore()
</script>

Store 参数

第一个参数 - - - id
仓库名称,必填,唯一性,习惯格式 use__Store
第二个参数 - - - 接受两种类型
Option 对象:带有 state | getters | actions 属性。
Setup 函数:ref() => state | computed() => getters | function() => actions

Setup Store

  1. 要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性。这意味着不能在 store 中使用私有属性
  2. Setup store 比 Option Store 带来了更多的灵活性,因为可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。
  3. Setup store 可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用 inject() 访问,就像在组件中一样。
// Option 对象 
defineStore('myPinia', {
  state: () => { return { } },
  getters: { },
  actions: { }
});

// Setup 函数
defineStore('myPinia', () => {
  const count = ref(0)
  const doubleCount = computed(() => { })
  function increment() { }
  
  return { count, doubleCount, increment }
})

解构 Store

storeToRefs 解构 store 中的 状态getter,跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性。‌
action非响应式 (不是 ref 或 reactive) 的属性 直接在 Store 中解构。

// 定义
import { defineStore } from 'pinia'
import { computed, ref } from 'vue';
export const useMyPiniaStore = defineStore('myPinia', () => {
  const count = ref(1);
  const doubleCount = computed(() => count.value * 2)
  const increment = () => { count.value++ }
  const unRective = '非响应式数据'

  return { count, doubleCount, increment, unRective }
});

// 解构
<script setup>
  import { useMyPiniaStore } from '../store/store'
  import { storeToRefs } from 'pinia'
  const store = useMyPiniaStore()
  const { count, doubleCount } = storeToRefs(store)
  const { increment, unRective } = store
</script>

State

// 方式1 -- 不设置状态类型
export const useMyPiniaStore = defineStore('myPinia', {
  state: () => { return { count: 0 } }
});

// 方式2 -- ts 严格模式 定义接口规定数据的类型
interface UserInfo {
  name: string
  age: number,
  count: number
}
defineStore('myPinia', {
  state: () => {
    return {
      userList: [] as UserInfo[],
      user: null as UserInfo | null,
      count: 0 as UserInfo['count']
    }
  }
})

访问 state

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

重置 state

// 选项式API(options API)
const store = useMyPiniaStore()
store.$reset()

// 组合式API(Composition API)
defineStore('myPinia', () => {
  const count = ref(0)
  const $reset = () => { count.value = 0 }
  
  return { count, $reset }
})

选项式 API 中的 mapState 和 mapWritableState

// mapState
import { mapState } from 'pinia'
import { useMyPiniaStore } from '../store/store'
export default {
  computed: {
    ...mapState(useMyPiniaStore, ['count'])  // this.count => store.count
    ...mapState(useMyPiniaStore, {
      myOwnName: 'count', // this.myOwnName => store.count
      double: store => store.count * 2 // 定义一个函数获取 store.count
    })
  }
}

// mapWritableState -- 不能像 mapState() 那样传递一个函数
export default {
  computed: {
    ...mapWritableState(useMyPiniaStore, ['count'])  // this.count => store.count
    ...mapWritableState(useMyPiniaStore, { myOwnName: 'count' }) // this.myOwnName => store.count
  }
}

对于像数组这样的集合,并不一定需要使用 mapWritableState(),mapState() 也允许你调用集合上的方法,除非想用 cartItems = [] 替换整个数组。

变更 state

// 1. patch 方法。允许用一个 state 的补丁对象在同一时间更改多个属性
store.$patch({
  count: store.count + 1,
  age: 120,
  name: 'DIO'
})

// 2. 有些变更很难实现或者很耗时:任何集合的修改(例如,向数组中添加、移除一个元素或是做 splice 操作)
//都需要创建一个新的集合。因此,$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。
store.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

替换 state

// 不能完全替换掉 store 的 state,因为那样会破坏其响应性。但是可以 patch 它。
store.$patch({ count: 66 })
// 也可以通过变更 pinia 实例的 state 来设置整个应用的初始 state。
pinia.state.value = {}

订阅 state

类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次。

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // 和 cartStore.$id 一样
  mutation.storeId // 'cart'
  // 只有 mutation.type === 'patch object'的情况下才可用
  mutation.payload // 传递给 cartStore.$patch() 的补丁对象。

  // 每当状态发生变化时,将整个 state 持久化到本地存储。
  localStorage.setItem('cart', JSON.stringify(state))
})

默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离。

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

可以在 pinia 实例上使用 watch() 函数侦听整个 state。

watch(
  pinia.state,
  (state) => {
    // 每当状态发生变化时,将整个 state 持久化到本地存储。
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)

Getter

Getter 完全等同于 store 的 state 的计算值。

// option对象 -- 定义getter
export const useMyPiniaStore = defineStore('myPinia', {
  state: () => ({
    count: 0
  }),
  getters: {
    //方式1 -- 基本使用
    doubleCount: (state) => state.count * 2,
    //方式2 -- 使用其他 getter,在 TypeScript 中必须定义返回类型。
     doublePlusOne(): number {
      return this.doubleCount + 1
    },
    //方式3 -- 返回一个函数
    countAddNum: (state) => {
      return (num: number) => state.count + num
    },
    //方法4 -- 访问其他 store 的getter
    otherGetter: (state) => {
      const otherStore = useOtherStore()
      return state.count + otherStore.count
    }
  }
})

在 setup() 中使用 getter

<script setup>
  import { useMyPiniaStore } from '../store/store'
  const store = useMyPiniaStore()
  store.count = 3
  store.doubleCount // 6
</script>

使用 mapState 辅助函数映射 getter

import { mapState } from 'pinia'
import { useMyPiniaStore } from '../store/store'
export default {
  computed: {
    ...mapState(useMyPiniaStore, ['doubleCount']), // this.doubleCount => store.doubleCount
    ...mapState(useMyPiniaStore, {
      myOwnName: 'doubleCount', // this.myOwnName => store.doubleCount
      double: (store) => store.doubleCount // double => store.doubleCount
    })
  }
}

Action

定义和使用 action

action 相当于组件中的 method。
action 可以是异步的。

// 定义 actions
export const useMyPiniaStore = defineStore('myPinia', {
  state: () => { return { count: 6 } },
  actions: {
    increment() { this.count++ },
    randomizeCounter() { this.count = Math.round(100 * Math.random()) },
    // 异步
    asyncCount() { setTimeout(() => this.count++, 1000) },
    // 接收参数
    countAddNumber(num: number) { this.count += num }
  }
});
// 使用 action
<template>
  <button @click="store.increment()">increment</button>
  <button @click="store.randomizeCounter()">randomizeCounter</button>
  <button @click="store.asyncCount()">asyncCount</button>
  <button @click="store.countAddNumber(100)">countAddNumber</button>
  <p>{{ store.count }}</p>
</template>

<script setup>
  import { useMyPiniaStore } from './store/store'
  const store = useMyPiniaStore()
</script>

mapActions

import { mapActions } from 'pinia'
import { useMyPiniaStore } from '../store/store'
export default {
  methods: {
    ...mapActions(useMyPiniaStore, ['increment']) // this.increment() => store.increment()
    ...mapActions(useMyPiniaStore, { myOwnName: 'increment' }) // this.myOwnName() => store.increment()
  },
}

订阅 action

通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。onError 允许你在 action 抛出错误 / reject 时执行一个回调函数。

const unsubscribe = useMyPiniaStore.$onAction(
  ({
    name, // action 名称
    store, // store 实例,类似 `useMyPiniaStore`
    args, // 传递给 action 的参数数组
    after, // 在 action 返回或解决后的钩子
    onError // action 抛出或拒绝的钩子
  }) => {
    const startTime = Date.now() // 为这个特定的 action 调用提供一个共享变量
    console.log(`Start "${name}" with params [${args.join(', ')}].`) // 这将在执行 "store "的 action 之前触发。
    after((result) => { // 这将在 action 成功并完全运行后触发。它等待着任何返回的 promise
      console.log(`Finished "${name}" after ${Date.now() - startTime}ms.\nResult: ${result}.`)
    })
    onError((error) => { // 如果 action 抛出或返回一个拒绝的 promise,这将触发
      console.warn(`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`)
    })
  }
)

// 手动删除监听器
unsubscribe()

默认情况下,action 订阅器会被绑定到添加它们的组件上(如果 store 在组件的 setup() 内)。当该组件被卸载时,它们将被自动删除。如果想在组件卸载后依旧保留它们,请将 true 作为第二个参数传递给 action 订阅器,将其从当前组件中分离。

<script setup>
  const someStore = useSomeStore()
  someStore.$onAction(callback, true) // 此订阅器即便在组件卸载之后仍会被保留
</script>

总结

Pinia的好处主要体现在简单易用、‌TypeScript支持、‌分治管理、‌性能优化等方面。‌
简单易用:‌Pinia的API设计简洁明了,‌易于理解和使用。‌与Vuex相比,‌它不需要定义复杂的store和module,‌而是采用类似于Vue组件的方式来处理状态管理,‌使得状态管理更加直观和易于上手。‌
TypeScript支持:‌Pinia内置了对TypeScript的支持,‌能够提供类型安全的状态管理,‌有效避免了类型错误的问题。‌这对于使用TypeScript开发的Vue项目来说,‌是一个重要的优势。‌
分治管理:‌Pinia支持将状态分割成多个模块,‌每个模块可以独立管理自己的状态。‌这种分治的设计使得应用状态更加清晰可控,‌有助于提高代码的可维护性和可读性。‌
性能优化:‌Pinia内部使用了响应式编程的技术,‌提高了状态管理的性能和响应速度。‌此外,‌它还支持使用插件来进行状态的持久化和调试,‌进一步提高了开发效率。‌
综上所述,‌Pinia作为一个基于Vue 3的状态管理库,‌通过其简洁的API设计、‌内置的TypeScript支持、‌模块化的状态管理以及性能优化等特点,‌为Vue项目的开发提供了高效且便捷的状态管理解决方案。‌

  • 37
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

baosy_web

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值