Pinia的使用

320 阅读2分钟

Pinia基本使用

Pinia的定义是使用pinia暴露出来的defineStore定义的

Setup Store

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

Option Store

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
      console.log('this.doubleCount', this.doubleCount);
    }
  }
})

使用

直接引入store的定义, 调用store导出的变量即可使用

解构需要使用pinia提供的storeToRefs来保证数据的响应性

<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter.js'

// 不使用解构方式
const counterStore = useCounterStore()

// 使用解构方式, 需要配合storeToRefs方法
const { count, doubleCount } = storeToRefs(counterStore)

const handleStoreIncrement = () => {
    counterStore.increment()
}

</script>
<template>xxxx</template>

State

state推荐使用返回对象的方式来创建

import { defineStore } from "pinia";

export const useStateStore = defineStore('myState', {
    state: () => {
        return {
            // 所有这些属性都将自动推断出它们的类型
            count: 0,
            name: 'Eduardo',
            isAdmin: true,
            items: [],
            hasChanged: true,
        }
    }
});

修改state

使用$patch, 可以传入对象, 更推荐传入回调函数, 在回调函数内修改state;
传入对象方式造成修改state很难实现或者耗时,比如修改数组时需要创建一个新的集合, 而回调函数的方式则避免了以上问题.

<script setup>
import { useStateStore } from '@/stores/state.js'

const stateStore = useStateStore()

// 传入对象方式
const handle$patch1 = () => {
    stateStore.$patch({
        name: 'hhhhh'
    })
}

// 传入回调函数方式
const handle$patch2 = () => {
    stateStore.$patch(state => {
        state.name = 'heiheihei';
    })
}
</script>

重置state

$reset方法可以重置state

const store = useStore()
store.$reset()

订阅state

使用subscribe() 方法侦听 state 及其变化,(比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次)

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

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

Getter完全等同于store中的state的计算值. 推荐使用箭头函数定义Getter

import { defineStore } from 'pinia'
import {useStateStore} from './state'

export const useGetterStore = defineStore('mygetter', {
    state: () => {
        return {
            count: 1
        }
    },
    getters: {
        // 访问state
        doubleCount(state) {
            return state.count * 2
        },
        // 访问其他getter
        tripleCount(state) {
            return state.count * this.doubleCount / this.doubleCount * 3
        },
        // 访问其他store的getter
        stateStoreCount(state) {
            const stateStore = useStateStore()
            return stateStore.count + state.count
        }
    }
});

访问state

如上例子, 直接访问state即可

访问其他getter

getter内的this能够访问本store下的其他getter, 例子如上

访问其他store的getter

直接引用其他store,直接使用即可, 例子如上

Action

action是在defineStore内actions对象内定义, action内可以进行异步操作

action定义

import { defineStore } from 'pinia'

// 模拟请求
const fetchMethod = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const randomNum = Math.random()
            const isInteger = Number.parseInt((randomNum * 100)) % 2 === 0
            if (isInteger) {
                resolve(randomNum)
            } else {
                reject(randomNum)
            }
        }, 1000)
    });
}

export const useActionStore = defineStore('myaction', {
    state: () => {
        return {
            count: 2
        }
    },
    actions: {
        async increment() {
            this.count = await fetchMethod()
            return this.count;
        }
    }
})

订阅action

可以通过 store.onAction()来监听action和它们的结果<br/>onAction() 来监听 action 和它们的结果<br/> onAction的回调函数是会在action本身之前执行, after会在promise解决后执行, onError会在抛出错误或者reject后执行
值得注意的是, 如上例子, await fetchMethod()后return了this.count, 这样才能在成功了后after内拿到result, 失败了后在onError内拿到error

<script setup>
import { useActionStore } from '@/stores/action.js'
const actionStore = useActionStore()

// 订阅action的变化
const unsubscribe = actionStore.$onAction(({name, store, args, after, onError}) => {
    console.log(name);
    console.log(store);
    console.log(args);

    after((result) => {
        console.log('result', result);
    });

    onError((error) => {
        console.log('error', error);
    });
})

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

Plugin

Plugin定义

Plugin是一个function, 调用pinia.use(pluginFunc)即可

export function myPiniaPlugin(context) {
  context.pinia // 用 `createPinia()` 创建的 pinia。 
  context.app // 用 `createApp()` 创建的当前应用(仅 Vue 3)。
  context.store // 该插件想扩展的 store
  context.options // 定义传给 `defineStore()` 的 store 的可选对象。
  // ...
}

例子:

import { ref, toRef } from "vue"
import { debounce } from 'lodash'

const sharedRef = ref('shared')

export const myPiniaPlugin = (context) => {
    // 扩展store, 为store新增共享的属性
    context.store.hello = ref('world')
    context.store.shared = sharedRef

    /**
     * 添加新的state
     * 1. $state上要添加, 才能被devtool识别, 才能被SSR正确序列化
     * 2. store上要添加, 才能store.myState上访问到
     */
    if (!Object.prototype.hasOwnProperty(context.store.$state, 'hasError')) {
        context.store.$state.hasError = ref(false)
    }
    context.store.hasError = toRef(context.store.$state, 'hasError')

    /**
     * 插件中可以正常使用store.$subscribe和store.$onAction
     */
    context.store.$subscribe(() => {
        // 响应 store 变化
    })
    context.store.$onAction(() => {
        // 响应 store actions
    })

    /**
     * 添加新的选项
     * 例如添加防抖
     */

    if (context.options.debounce) {
        return Object.keys(context.options.debounce).reduce((debounceAction, action) => {
            console.log(debounceAction, action);
            debounceAction[action] = debounce(
                context.store[action],
                context.options.debounce[action]
            )
            return debounceAction
        }, {})
    }

}

扩展Store

如上例子

添加新的state

  • 在 store 上,然后你才可以用 store.myState 访问它。
  • 在 store.$state 上,然后你才可以在 devtools 中使用它,并且,在 SSR 时被正确序列化(serialized)。

如上例子

插件中调用 $subscribe

如上例子

添加新的选项

如上例子, 同时store内需要提供需要的内容(如debounce这个option的定义)

import { defineStore } from 'pinia'

export const usePluginsBaseStore = defineStore('mypluginsbase', {
    state: () => {
        return {
            count: 10
        }
    },
    actions: {
        searchContacts() {
            console.log('searchContacts');
        },
    },
    // 这将在后面被一个插件读取
    debounce: {
        searchContacts: 300
    }
})