快速入门Pinia

安装

npm install pinia -S

main.js引入

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

创建 /src/store/user.js 文件

1. 通过选项式创建

defineStore

  • 函数第一个参数是唯一ID(名字)
  • 第二个参数是一个对象
    • state 是 store 的数据 (data)
    • getters 是 store 的计算属性 (computed)
    • actions 是方法 (methods)
import { defineStore } from "pinia";

export const useUserStore = defineStore("user", {
  state: () => { 
    return {
      userInfo: null,
    };
  },
  getters: {},
  actions: {},
})

2. 通过组合式API创建

  • 第二个参数是一个函数
    • ref() 是 state 属性
    • computed() 是getters
    • function() 是 actions
export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  function getUserName() {
    user.value.userName
  }

  return { user, getUserName }
})

通过两种方式都可以创建一个store,你不需要纠结用哪种,你喜欢就行

使用state

在vue文件中使用

<template>
    <div>
        {{ userStore.userInfo }}
    </div>
</template>

<script setup>
    import { useUserStore } from '@/store/user'
    let userStore = useUserStore()
    console.log(userStore.userInfo)
</script>

使用storeToRefs函数解构

<script setup>
    import { storeToRefs } from 'pinia'
    import { useUserStore } from '@/store/user'
    let userStore = useUserStore()

    const { userInfo } = userStore // 破坏了响应性, userName始终不会变

    // 可以这样写,他是响应式的
    const userInfo = computed(() => userStore.userInfo)
    
    // 或者使用storeToRefs函数解构
    const { userInfo } = storeToRefs(userStore) // 这样是响应式的
</script>

变更 state

重置 state

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

const store = useStore()

store.$reset()

直接修改

<script setup>
    import { useUserStore } from '@/store/user'
    let userStore = useUserStore()
    userStore.userInfo = { userName: '张三' }
    userStore.userInfo.userName = '李四'
</script>

通过 $patch 修改

// 通过$patch可以修改一个或多个state

store.$patch({
  count: store.count + 1,
  age: 120,
  name: 'DIO',
})

// 也许你想对数组进行增删改,那么可以使用函数的形式

store.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

通过actions

import { useUserStore } from '@/store/user'
let userStore = useUserStore()
userStore.setUserInfo({ userName: '张三' })

替换 state

你不能完全替换掉 store 的 state,因为那样会破坏其响应性。但是,你可以 patch 它

// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })

你也可以通过变更 pinia 实例的 state 来设置整个应用的初始 state。这常用于 SSR 中的激活过程

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>

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

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

getters

大多数时候,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

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

调用其他store中的getter

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

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

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

向 getter 传递参数

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

export const useStore = defineStore('main', {
  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>

请注意这样做 getter 将不再被缓存,它们只是一个被你调用的函数。不过,你可以在 getter 本身中缓存一些结果,虽然这种做法并不常见,但有证明表明它的性能会更好:

export const useStore = defineStore('main', {
  getters: {
    getActiveUserById(state) {
      const activeUsers = state.users.filter((user) => user.active)
      return (userId) => activeUsers.find((user) => user.id === userId)
    },
  },
})

actions

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

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

调用其他store中的action

想要使用另一个 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')
      }
    },
  },
})

订阅 action​

你可以通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。同样地,onError 允许你在 action 抛出错误或 reject 时执行一个回调函数。这些函数对于追踪运行时错误非常有用,类似于Vue docs 中的这个提示。
这里有一个例子,在运行 action 之前以及 action resolve/reject 之后打印日志记录

const unsubscribe = someStore.$onAction(
  ({
    name, // action 名称
    store, // store 实例,类似 `someStore`
    args, // 传递给 action 的参数数组
    after, // 在 action 返回或解决后的钩子
    onError, // action 抛出或拒绝的钩子
  }) => {
    // 为这个特定的 action 调用提供一个共享变量
    const startTime = Date.now()
    // 这将在执行 "store "的 action 之前触发。
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // 这将在 action 成功并完全运行后触发。
    // 它等待着任何返回的 promise
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // 如果 action 抛出或返回一个拒绝的 promise,这将触发
    onError((error) => {
      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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值