Loading

Vue3 状态管理之pinia

什么是Pinia

在vue3之前我们最常用的状态管理就是Vuex,当然在vue3中也有相对应的Vuex的版本。但是还是推荐使用Pinia,原因有以下几点:

  1. 语法简单,mutations 不再存在。
  2. 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的
  3. 不再需要注入、导入函数、调用函数、享受自动完成功能!
  4. 无需动态添加 Store,默认情况下它们都是动态的
  5. 不再有 modules 的嵌套结构。
  6. 没有 命名空间模块。

Pinia使用

安装

yarn add pinia
# 或者使用 npm
npm install pinia

创建pinia

main.js中创建pinia并挂载。

import { createPinia } from 'pinia'
app.use(createPinia())

定义一个Store

src/store/index.js里引入pinia,并且导出。

Store 是使用 defineStore() 定义的,并且它需要一个唯一名称,作为第一个参数传递:

import { defineStore } from "pinia";

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{

})

在页面中调用

import {useStore} from "../store/index.js";
const store = useStore()
console.log('store',store)

一旦 store 被实例化,你就可以直接在 store 上访问 stategetters 和 actions 中定义的任何属性。

State

大多数时候,state 是 store 的核心部分。 我们通常从定义应用程序的状态开始。 在 Pinia 中,状态被定义为返回初始状态的函数。 Pinia 在服务器端和客户端都可以工作。

  • 创建state
import { defineStore } from "pinia";

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
    // 推荐使用 完整类型推断的箭头函数
    state: () => {
        return {
            // 所有这些属性都将自动推断其类型
            // 定义一个是否展开的字段isCollapse
            isCollapse:false,
            counter: 0,
        }
    }
})
  • 访问 state
<template>
    <p>获取的值:{{store.counter}}</p>
</template>
<script setup>
import {useStore} from "../store/index.js";
const store = useStore()
console.log('store',store.counter)
</script>
  • 重置状态 可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:
<template>
    <p>获取的值:{{store.counter}}</p>
    <button @click="reset">重置</button>
</template>
<script setup>
import {useStore} from "../store/index.js";
const store = useStore()
store.counter++
const reset = () => {
  store.$reset()
}
</script>
  • 改变状态 --$patch

除了直接用 store.counter++ 修改 store,你还可以调用 $patch 方法。 它允许您使用部分“state”对象同时应用多个更改:

<template>
   <button @click="open">展开</button>
</template>
<script setup>
import {useStore} from "../store/index.js";
const store = useStore()
const open = () => {
  store.$patch({
    // 修改你要修改的数据
    isCollapse: !store.isCollapse
  })
}
</script>

或者

const open = () => {
  store.$patch( (state) => {
    state.isCollapse = !store.isCollapse
  })
}
  • 改变状态 -- actions

可以在pinia中创建修改方法

import { defineStore } from "pinia";

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
    // 推荐使用 完整类型推断的箭头函数
    state: () => {
        return {
            // 所有这些属性都将自动推断其类型
            // 定义一个是否展开的字段isCollapse
            isCollapse: true,
            counter: 0,
        }
    },
    actions: {
        updateCollapse() {
            this.isCollapse = !this.isCollapse
        }
    }

})

在页面调用updateCollapse方法,页面同上就省略多余代码

const open = () => {
  store.updateCollapse()
}
  • 订阅状态

以通过 store 的$subscribe()方法查看状态及其变化,类似于 Vuex 的 subscribe 方法。 与常规的 watch() 相比,使用 $subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次(例如,当使用上面的函数版本时)。

$subscribe 监听store数据修改 当数据改变了 那么subscribe也会触发。

<script setup>
import {useStore} from "../store/index.js";

const store = useStore()
const open = () => {
  store.$patch({
    // 修改你要修改的数据
    isCollapse: !store.isCollapse
  })
}
// 监听store数据修改
let subscribe = store.$subscribe( (params,state) => {
  console.log('params',params)
  console.log('state',state)
  // 每当它发生变化时,将整个状态持久化到本地存储
  localStorage.setItem('collapseState', state.isCollapse)
})
</<script>

getters

Getter 完全等同于 Store 状态的 计算值。 它们可以用defineStore()中的 getters 属性定义。 他们接收“状态”作为第一个参数以鼓励箭头函数的使用:

import { defineStore } from "pinia";

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
    // 推荐使用 完整类型推断的箭头函数
    state: () => {
        return {
            // 所有这些属性都将自动推断其类型
            // 定义一个是否展开的字段isCollapse
            isCollapse: false,
            counter: 1,
        }
    },
    getters: {
        // 自动将返回类型推断为数字
        doubleCount: (state) => state.counter * 2,
    },
    actions: {
        updateCollapse() {
            this.isCollapse = !this.isCollapse
        }
    }
})

你可以直接在 store 实例上访问 getter

<p>Double count is {{ store.doubleCount }}</p>
  • 访问其他getter

与计算属性一样,您可以组合多个 getter。 通过 this 访问任何其他 getter

import { defineStore } from "pinia";

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
    // 推荐使用 完整类型推断的箭头函数
    state: () => {
        return {
            // 所有这些属性都将自动推断其类型
            // 定义一个是否展开的字段isCollapse
            isCollapse: false,
            counter: 1,
        }
    },
    getters: {
        // 自动将返回类型推断为数字
        doubleCount: (state) => state.counter * 2,
        doubleCountPlusOne() {
            // 自动完成 
            return this.doubleCount + 1
        },
    },
    actions: {
        updateCollapse() {
            this.isCollapse = !this.isCollapse
        }
    }
})
  • 将参数传递给 getterGetters 只是幕后的 computed 属性,因此无法向它们传递任何参数。 但是,您可以从 getter 返回一个函数以接受任何参数:
export const useStore = defineStore('main', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})

在组价中使用

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

    return { getUserById: store.getUserById }
  },
}
</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
    },
  },
})

Actions

Actions 相当于组件中的 methods。 它们可以使用 defineStore() 中的 actions 属性定义,并且它们非常适合定义业务逻辑,下面来举个例子:

import { defineStore } from "pinia";

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
    // 推荐使用 完整类型推断的箭头函数
    state: () => {
        return {
            // 所有这些属性都将自动推断其类型
            // 定义一个是否展开的字段isCollapse
            isCollapse: false,
            counter: 1,
            userData: null
        }
    },
    getters: {
        // 自动将返回类型推断为数字
        doubleCount: (state) => state.counter * 2,
        doubleCountPlusOne() {
            // 自动完成 
            return this.doubleCount + 1
        },
    },
    actions: {
        updateCollapse() {
            this.isCollapse = !this.isCollapse
        },
         increment() {
            this.counter++
        },
        randomizeCounter() {
            this.counter = Math.round(100 * Math.random())
        },
        // 异步调用
        async registerUser(login,password) {
            try {
                this.userData = await api.post({ login, password})
                console.log(`Welcome back`,this.userData.name)
            } catch (error) {
                return error
            }
        }
    }
})

Actions 在页面上的调用:

import {useStore} from "../store/index.js";

const store = useStore()
const add = () => {
  //store.increment()
  store.randomizeCounter()
}
  • 访问其他 store 操作
import { useAuthStore } from './auth-store'

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

可以使用 store.$onAction() 订阅 action 及其结果。 传递给它的回调在 action 之前执行。 after 处理 Promise 并允许您在 action 完成后执行函数。

const unsubscribe = someStore.$onAction(
  ({
    name, // action 的名字
    store, // store 实例
    args, // 调用这个 action 的参数
    after, // 在这个 action 执行完毕之后,执行这个函数
    onError, // 在这个 action 抛出异常的时候,执行这个函数
  }) => {
    // 记录开始的时间变量
    const startTime = Date.now()
    // 这将在 `store` 上的操作执行之前触发
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

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

    // 如果 action 抛出或返回 Promise.reject ,onError 将触发
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// 手动移除订阅
unsubscribe()

pinia 模块化

pinia中定义模块 只需要定义多个store即可 因为pinia没有单一数据源这个概念 在其中可以定义多个store对象。

<script setup>
// 引用第一个store
import {useCommonStore} from "../store/common"
// 引用第2个store
import {useUserStore} from "../store/user"
let commonStore = useCommonStore()
let userStore = useUserStore()
</script>
posted @ 2023-02-28 15:22  RuoVea  阅读(358)  评论(0编辑  收藏  举报