setup pinia 使用指南

基于使用 setup() 时的用法记录

定义Store

defineStore 用来定义定义 Store, 第一个参数为 Store 的id, 用于区分不同模块
state 定义数据,相当于vue中的data, 为了完整类型推理,推荐使用箭头函数;
getters 对 state中数据进行计算处理, 相当于vue中的 computed
action 相当于vue中的 method, 可以写同步和异步方法

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

interface UserInfo {
    name: string
    age: number
}
interface State {
    count: number;
    user: UserInfo | null;
    userList: UserInfo[];
}
export const useLoginStore = defineStore('main', {
    // 为了完整类型推理,推荐使用箭头函数
  state: ():State => {
    return {
        count:1,
        user:null,
        userList:[],
    }
  },

  // 计算属性
  getters: {
    // 自动推断出返回类型是一个 number
    doubleCount: (state) => state.count * 2,

    // 可以用 this 来引用 getter
    doublePlusOne() {
        // 整个 store 的 自动补全和类型标注
        return this.doubleCount + 1
    },

    // 可以从 getter 返回一个函数,该函数可以接受任意参数
    countAddNum: (state) => {
        let count = state.count; // 这里形成闭包,变量会被缓存
        return (num) => state.count + num + count
    },

    // 访问其他 store 的 getter
    otherGetter(state) {
        const otherStore = useOtherStore()
        return state.count + otherStore.data
    },
  },

    // Action 相当于组件中的 method。它们也是定义业务逻辑的完美选择
   actions: {
        // 同步的
        increment() {
            this.count++
        },
        randomizeCounter() {
            this.count = Math.round(100 * Math.random())
        },

        // 异步的
        async registerUser(login, password) {
            try {
                this.user = await api.post({ login, password })
                alert(`Welcome back ${this.user.name}!`)
            } catch (error) {
                alert(error)
                // 让表单组件显示错误
                return error
            }
        },
        // 使用其他 store 
        async fetchUserPreferences() {
            const auth = useOtherStore()
            if (auth.isAuthenticated) {
                this.preferences = await fetchPreferences()
            } else {
                throw new Error('User must be authenticated')
            }
        },
    },
});

用法

使用 setup() 时的用法

<script setup lang="ts">
import { useLoginStore } from "@/store/useLoginStore"
const store = useLoginStore();
console.log(store.count)
script>

state

你可以通过 store 实例访问 state,直接对其进行读写

访问

console.log(store.count)

修改

直接修改

store.count++

批量修改
$patch 方法 允许你用一个 state 的补丁对象在同一时间更改多个属性

store.$patch({
  count: store.count + 1,
  user: null,
})

重置
使用选项式 API 时,你可以通过调用 store 的 $reset() 方法将 state 重置为初始值。

store.$reset()

监听 订阅 state 变化

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

store.$subscribe((mutation, state) => {
    console.log(mutation.storeId) //  main  // 和 store.$id 一样 
    console.log(mutation.type) // 'direct' | 'patch object' | 'patch function'
  
    // 只有 mutation.type === 'patch object'的情况下才可用
    console.log(mutation.payload) // 传递给 store.$patch() 的补丁对象。

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

如果你想在组件卸载后依旧保留它们,设置第二个参数 { detached: true }

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

持久化

对整个 state 持久化

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

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

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

pinia.state.value = JSON.parse(localStorage.getItem('piniaState')) || {}

对指定 store 进行持久化

每当状态发生变化时,将整个 state 持久化到本地存储。

store.$subscribe((mutation, state) => {
    localStorage.setItem('mainStore', JSON.stringify(state))
});

初始化时回显持久化数据

state: ():State => {
    return {
        count:1,
        user:null,
        userList:[],
        ...(JSON.parse(localStorage.getItem('mainStore')) || {})
    }
},

App.vue 中回显缓存数据

import { useLoginStore } from "@/store/useLoginStore"
const store = useLoginStore();
store.$patch(JSON.parse(localStorage.getItem('mainStore')))

getter

作为 store 的一个属性,你可以直接访问任何 getter(与 state 属性完全一样)

const store = useLoginStore();
console.log(store.count ) // 读取 state
console.log(store.doubleCount ) // 读取getter

actions

Action 可以像函数或者通常意义上的方法一样被调用

const store = useLoginStore();
store.randomizeCounter()

在模板中也可以

<button @click="store.randomizeCounter()">生成随机数button>

订阅 action

你可以通过 store.$onAction() 来监听 action 和它们的结果。
传递给它的回调函数会在 action 本身之前执行。

const store = useLoginStore();
store.$onAction(callback)

如果你想在组件卸载后依旧保留它们,请将 true 作为第二个参数,以便将其从当前组件中分离

const store = useLoginStore();
store.$onAction(callback, true)

after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。
onError 允许你在 action 抛出错误或 reject 时执行一个回调函数。
这些函数对于追踪运行时错误非常有用,类似于Vue docs 中的这个提示。

这里有一个例子,在运行 action 之前以及 action resolve/reject 之后打印日志记录。

const unsubscribe = store.$onAction(
  ({
    name, // action 名称
    store, // store 实例,类似 `store`
    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()

单页面应用中使用

main.ts

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import { useLoginStore } from "@/store/useLoginStore"

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

// app 安装 pinia 插件后使用
const store = useLoginStore();
console.log(store.count)

App.vue

<script setup lang="ts">
import { useLoginStore } from "@/store/useLoginStore"
const store = useLoginStore();
console.log(store.count)
script>

Vue Router 的导航守卫中使用 store 的例子

import { createRouter } from 'vue-router'
import { useLoginStore } from "@/store/useLoginStore"

const router = createRouter({
  // ...
})

// ❌ 由于引入顺序的问题,这将失败
// const store = useStore()

router.beforeEach((to) => {
  // ✅ 这样做是可行的,因为路由器是在其被安装之后开始导航的,
  // 而此时 Pinia 也已经被安装。
  const store = useLoginStore();

  if ( !store.user) return '/login'
})

你可能感兴趣的:(nvvm,前端,vue.js,pinia)