Vue3官方生态Pinia的使用

175 阅读4分钟

开始

安装

npm install pinia

注册插件

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const pinia = createPinia()
const app = createApp(App)
​
app.use(pinia)
app.mount('#app')

核心概念

定义store

  • Store() 的定义是通过 defineStore() ,它的第一个参数要求是一个唯一不可重复的名字
import { defineStore } from 'pinia'// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// 第一个参数是你的应用中 Store 的唯一 名字。
export const useXXXXXStore = defineStore('unique', {
  // 其他配置...
})
  • 第一个参数

    • 必须传入的, Pinia 将用它来连接 store 和 devtools
  • 第二个参数

    • 可接受两类值:Setup 函数或 Option 对象

Option Store

Vue 的选项式 API 类似

可以传入一个带有 stateactionsgetters 属性的 Option 对象

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})
  • state 是 store 的数据 (data)
  • getters 是 store 的计算属性 (computed)
  • actions 则是方法 (methods)

Setup Store

Vue 组合式 API 的 setup 函数相似

  • 我们可以传入一个函数,该函数定义了一些响应式属性和方法
  • 并且返回一个带有我们想暴露出去的属性和方法的对象
export const useCounterStore = defineStore('counter', () => {
  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 中使用私有属性。

两者有什么区别

  • Setup store 比 Option Store带来了更多的灵活性

    • 因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数
  • Setup store 也可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用 inject() 访问,就像在组件中一样

import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'export const useSearchFilters = defineStore('search-filters', () => {
  const route = useRoute()
  // 这里假定 `app.provide('appProvided', 'value')` 已经调用过
  const appProvided = inject('appProvided')
​
  // ...
​
  return {
    // ...
  }
})

你应该选用哪种语法?

两种语法都有各自的优势和劣势。Option Store 更容易使用,而 Setup Store 更灵活和强大

使用 Store

<script setup>
import { useXXXXXStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useXXXXXStore()
</script>
  • 请注意,store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value

  • 就像 setup 中的 props 一样,我们不能对它进行解构

    • 解构会破环它的响应式
<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'const store = useCounterStore()
// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
const { name, doubleCount } = store
name // 将始终是 "Eduardo"
doubleCount // 将始终是 0
setTimeout(() => {
  store.increment()
}, 1000)
// ✅ 这样写是响应式的
// 💡 当然你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>

从 Store 解构

保持响应式需要使用storeToRefs()

  • 当你只使用 store 的状态而不调用任何 action 时,它会非常有用
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>

State

基本使用

相当于组件的状态

  • state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。
import { defineStore } from 'pinia'const useStore = defineStore('storeId', {
  // 为了完整类型推理,推荐使用箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断出它们的类型
      count: 0,
      name: 'Eduardo',
      isAdmin: true,
      items: [],
      hasChanged: true,
    }
  },
})

某些情况下可能推断不出来,只能自己定义接口了

interface State {
  userList: UserInfo[]
  user: UserInfo | null
}
​
interface UserInfo {
  name: string
  age: number
}
​
const useStore = defineStore('storeId', {
  state: (): State => {
    return {
      userList: [],
      user: null,
    }
  },
})

访问 state

  • 默认情况下,你可以通过 store 实例访问 state,直接对其进行读写
  • 如果没有在 state() 中被定义,则不能被添加
const store = useStore()
​
store.count++

重置state

通过调用 store 的 $reset() 方法将 state 重置为初始值

使用选项式 API 时
const store = useStore()
​
store.$reset()
在 Setup Stores中

需要创建自己的 $reset() 方法

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  function $reset() {
    count.value = 0
  }
  return { count, $reset }
})

变更state

传入对象
store.$patch({
  count: store.count + 1,
  age: 120,
  name: 'DIO',
})
传入函数
store.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

替换state

  • 完全替换掉 store 的 state
  • 只能使用$patch
// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })

订阅state

类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptionspatch 后只触发一次 (例如,当使用上面的函数版本时)。

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))
})

Getter

基本使用

  • 相当于计算属性

    • 推荐使用箭头函数,并且它将接收 state 作为第一个参数

    • 有可能也会不使用自己的state,会使用自己的getter,在一个基础上,计算出另一个

      • 通过this 可以访问到整个 store 实例

比如

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    // 自动推断出返回类型是一个 number   
    doubleCount(state) {
      return state.count * 2
    },
    // 返回类型必须明确设置   这是依赖上一个在进行处理
    doublePlusOne(): number {
      // 整个 store 的 自动补全和类型标注 ✨
      return this.doubleCount + 1
    },
  },
})

组件内部使用getter

<script setup>
import { useCounterStore } from './counterStore'
const store = useCounterStore()
</script>
​
<template>
  <p>Double count is {{ store.doubleCount }}</p>
</template>

向 getter 传递参数

  • Getter 只是幕后的计算属性,所以不可以向它们传递任何参数
  • 不过,你可以从 getter 返回一个函数,该函数可以接受任意参数

看一下实现

export const useUserListStore = defineStore('userList', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})

在组件使用

<script setup>
import { useUserListStore } from './store'
const userList = useUserListStore()
const { getUserById } = storeToRefs(userList)
// 请注意,你需要使用 `getUserById.value` 来访问
// <script setup> 中的函数
</script>
​
<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>

访问其他 store 的 getter

  • 导入别的仓库
  • 直接使用就完事了
import { useOtherStore } from './other-store'export const useStore = defineStore('main', {
  state: () => ({
    // ...
  }),
  getters: {
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.localData + otherStore.data
    },
  },
})

组件使用方法

<script setup>
const store = useCounterStore()
store.count = 3
store.doubleCount // 6
</script>

Action

action相当于方法

  • 通过 defineStore() 中的 actions 属性来定义
  • 并且它们也是定义业务逻辑的完美选择
export const useCounterStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++
    },
    randomizeCounter() {
      this.count = Math.round(100 * Math.random())
    },
  },
})

注意

  • 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 可以像函数或者通常意义上的方法一样被调用

<script setup>
const store = useCounterStore()
// 将 action 作为 store 的方法进行调用
store.randomizeCounter()
</script>
<template>
  <!-- 即使在模板中也可以 -->
  <button @click="store.randomizeCounter()">Randomize</button>
</template>

访问其他 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')
      }
    },
  },
})

以上就是本次文章的全部内容,如有帮助,记得点赞收藏,感谢您的观看。