Vue3——第十四章(Pinia:值得你喜欢的 Vue Store)

一、Pinia 简介

  • Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。
  • Pinia 起源于一次探索 Vuex 下一个迭代的实验,因此结合了 Vuex 5 核心团队讨论中的许多想法。
  • mutation 已被弃用。它们经常被认为是极其冗余的。它们初衷是带来 devtools 的集成方案,但这已不再是一个问题了。
  • Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的。
  • 极致轻量化:Pinia 大小只有 1kb 左右
  • 开发工具支持:不管是 Vue 2 还是 Vue 3,支持 Vue devtools 钩子的 Pinia 都能给你更好的开发体验。
  • 类型安全:类型可自动推断,即使在 JavaScript 中亦可为你提供自动补全功能!
  • Pinia 起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。
  • 从那时起,就倾向于同时支持 Vue 2 和 Vue 3,并且不强制要求开发者使用组合式 API。

二、基础示例

  • 下面就是 pinia API 的基本用法
  • 你可以先创建一个 Store:
// stores/counter.js
import {
    
     defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
    
    
  state: () => {
    
    
    return {
    
     count: 0 }
  },
  // 也可以这样定义
  // state: () => ({ count: 0 })
  actions: {
    
    
    increment() {
    
    
      this.count++
    },
  },
})
  • 为实现更多高级用法,你甚至可以使用一个函数(与组件 setup() 类似)来定义一个 Store:
export const useCounterStore = defineStore('counter', () => {
    
    
  const count = ref(0)
  function increment() {
    
    
    count.value++
  }

  return {
    
     count, increment }
})
  • 然后你就可以在一个组件中使用该 store 了:
import {
    
     useCounterStore } from '@/stores/counter'

export default {
    
    
  setup() {
    
    
    const counter = useCounterStore()

    counter.count++
    // 带有自动补全 ✨
    counter.$patch({
    
     count: counter.count + 1 })
    // 或者使用 action 代替
    counter.increment()
  },
}
  • 如果你还不熟悉 setup() 函数和组合式 API,Pinia 也提供了一组类似 Vuex 的 映射 state 的辅助函数,通过 mapStores()、mapState() 或 mapActions() 访问:
export default {
    
    
  computed: {
    
    
    // 其他计算属性
    // ...
    // 允许访问 this.counterStore 和 this.userStore
    ...mapStores(useCounterStore, useUserStore)
    // 允许读取 this.count 和 this.double
    ...mapState(useCounterStore, ['count', 'double']),
  },
  methods: {
    
    
    // 允许读取 this.increment()
    ...mapActions(useCounterStore, ['increment']),
  },
}
  • 在非组件的 js 文件中使用:

import {
    
     useCounterStore } from '@/stores/counter'

function xxxx = () =>{
    
    
   const counter = useCounterStore()
   counter.count++
   counter.increment()
}

三、核心概念

1、定义 Store

  • 我们得知道 Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字:
import {
    
     defineStore } from 'pinia'

// 你可以对 `defineStore()` 的返回值进行任意命名
// 但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useStore = defineStore('main', {
    
    
  // 其他配置...
})
  • 这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。
  • defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。
  • 与 Vue 的选项式 API 类似,我们也可以传入一个带有 state、actions 与 getters 属性的 Option 对象:
// 可以认为 state 是 store 的数据 (data)
//getters 是 store 的计算属性 (computed)
//而 actions 则是方法 (methods)
export const useCounterStore = defineStore('counter', {
    
    
  state: () => ({
    
     count: 0 }),
  getters: {
    
    
    double: (state) => state.count * 2,
  },
  actions: {
    
    
    increment() {
    
    
      this.count++
    },
  },
})
  • 与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
export const useCounterStore = defineStore('counter', () => {
    
    
  const count = ref(0)
  function increment() {
    
    
    count.value++
  }

  return {
    
     count, increment }
})
  • 在 Setup Store 中:ref() 就是 state 属性、computed() 就是 getters、function() 就是 actions
  • Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。

2、State

  • 在大多数情况下,state 都是你的 store 的核心。
  • 人们通常会先定义能代表他们 APP 的 state。
  • 在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。
import {
    
     defineStore } from 'pinia'

const useStore = defineStore('storeId', {
    
    
  // 为了完整类型推理,推荐使用箭头函数
  state: () => {
    
    
    return {
    
    
      // 所有这些属性都将自动推断出它们的类型
      count: 0,
      name: 'Eduardo',
      isAdmin: true,
      items: [],
      hasChanged: true,
    }
  },
})
  • 默认情况下,你可以通过 store 实例访问 state,直接对其进行读写。
const store = useStore()

store.count++
  • 可以通过调用 store 的 $reset() 方法将 state 重置为初始值。
const store = useStore()

store.$reset()
  • 除了用 store.count++ 直接改变 store,你还可以调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性:
store.$patch({
    
    
  count: store.count + 1,
  age: 120,
  name: 'DIO',
})
  • $patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。
store.$patch((state) => {
    
    
  state.items.push({
    
     name: 'shoes', quantity: 1 })
  state.hasChanged = true
})
  • 你不能完全替换掉 store 的 state,因为那样会破坏其响应性。但是,你可以 patch 它。
// 这实际上并没有替换`$state`
store.$state = {
    
     count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({
    
     count: 24 })

3、Getter

  • Getter 完全等同于 store 的 state 的计算值。
  • 可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:
export const useStore = defineStore('main', {
    
    
  state: () => ({
    
    
    count: 0,
  }),
  getters: {
    
    
    doubleCount: (state) => state.count * 2,
  },
})
  • 大多数时候,getter 仅依赖 state,不过,有时它们也可能会使用其他 getter。
  • 因此,即使在使用常规函数定义 getter 时,我们也可以通过 this 访问到整个 store 实例
export const useStore = defineStore('main', {
    
    
  state: () => ({
    
    
    count: 0,
  }),
  getters: {
    
    
    // 自动推断出返回类型是一个 number
    doubleCount(state) {
    
    
      return state.count * 2
    },
    // 返回类型**必须**明确设置
    doublePlusOne(): number {
    
    
      // 整个 store 的 自动补全和类型标注 ✨
      return this.doubleCount + 1
    },
  },
})
  • 然后你可以直接访问 store 实例上的 getter 了:
<template>
  <p>Double count is {
    
    {
    
     store.doubleCount }}</p>
</template>

<script>
export default {
    
    
  setup() {
    
    
    const store = useStore()

    return {
    
     store }
  },
}
</script>

4、Action

  • Action 相当于组件中的 method。
  • 它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。
export const useStore = defineStore('main', {
    
    
  state: () => ({
    
    
    count: 0,
  }),
  actions: {
    
    
    increment() {
    
    
      this.count++
    },
    randomizeCounter() {
    
    
      this.count = Math.round(100 * Math.random())
    },
  },
})
  • 类似 getter,action 也可通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全✨)。
  • 不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action!
import {
    
     mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
    
    
  state: () => ({
    
    
    userData: null,
    // ...
  }),

  actions: {
    
    
    async registerUser(login, password) {
    
    
      try {
    
    
        this.userData = await api.post({
    
     login, password })
        showTooltip(`Welcome back ${
      
      this.userData.name}!`)
      } catch (error) {
    
    
        showTooltip(error)
        // 让表单组件显示错误
        return error
      }
    },
  },
})
  • Action 可以像方法一样被调用:
export default defineComponent({
    
    
  setup() {
    
    
    const main = useMainStore()
    // 作为 store 的一个方法调用该 action
    main.randomizeCounter()

    return {
    
    }
  },
})
  • 想要使用另一个 store 的话,那你直接在 action 中调用就好了:
import {
    
     useAuthStore } from './auth-store'

export const useSettingsStore = defineStore('settings', {
    
    
  state: () => ({
    
    
    preferences: null,
    // ...
  }),
  actions: {
    
    
    async fetchUserPreferences() {
    
    
      const auth = useAuthStore()
      if (auth.isAuthenticated) {
    
    
        this.preferences = await fetchPreferences()
      } else {
    
    
        throw new Error('User must be authenticated')
      }
    },
  },
})

猜你喜欢

转载自blog.csdn.net/weixin_44733660/article/details/128675886