vue工具之Pinia 安装与使用
Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念,state、getter 和 action,我们可以假设这些概念相当于组件中的 data
、 computed
和 methods
。
一、安装
#npm npm install pinia --save
#yarn
yarn add pinia
❗️提示:
如果您的应用使用 Vue 2,您还需要安装组合 API:@vue/composition-api。
Vue3.x版本使用
import { createPinia } from 'pinia' //使用 app.use(createPinia())
Vue2.x版本使用
import { createPinia, PiniaVuePlugin } from 'pinia' Vue.use(PiniaVuePlugin) const pinia = createPinia() new Vue({ el: '#app', // 其他选项... // ... // 注意同一个 `pinia` 实例可以在多个 Vue 应用程序中使用 // 同一个页面 pinia, })
二、使用
Pinia 的目录通常被称为 stores 而不是 store, 这是为了强调 Pinia 使用多个 store,而不是 Vuex 中的单个 store,同时也有迁移期间 Pinia 和 Vuex 混合使用的考虑。
1. store
创建store
☘️ 选项式
// src/stores/index.js // 引入Store定义函数 import { defineStore } from 'pinia' // 定义Store实例并导出,useStore可以是任何东西,比如useUser, useCart // 第一个参数,唯一不可重复,字符串类型,作为仓库ID 以区分仓库 // 第二个参数,以对象形式配置仓库的state,getters,actions export const useStore = defineStore('main', { // state 推荐箭头函数,为了TS类型推断 state: () => { return { name: '张三', counter: 0 } }, getters: {}, actions: {} })
☘️ 组合式
// src/stores/index.js // 引入Store定义函数 import { defineStore } from 'pinia' import { ref } from 'vue' // 定义Store实例并导出,useStore可以是任何东西,比如useUser, useCart // 第一个参数,唯一不可重复,字符串类型,作为仓库ID 以区分仓库 // 第二个参数,以函数形式配置仓库的state,getters,actions export const useStore = defineStore('main', ()=>{ const name = ref('张三') // 这里加ref 或者 不加 好像没有意义。 const counter = ref(1) // 这里加ref 或者 不加 好像没有意义。 return {name, counter} })
使用store
<template> <div> <button @click="handleClick">修改状态数据</button> <!-- 模板内不需要加.value --> <p>{{ store.name }}</p> <!-- computed获取 --> <p>{{ name }}</p> <!-- 或者使用解构之后的 --> <p>{{ counter }}</p> </div> </template> <script setup> import {computed,reactive} from 'vue' import { useStore } from "@/stores/index.js"; // 使普通数据变响应式的函数 import { storeToRefs } from "pinia"; // 1. 通过 useStore 获取到 定义的相关的 被 reactive 包装的包含store, getter, methods等内容的对象 const store = useStore(); // 2.结合computed获取 const name = computed(() => store.name); // 3.解构并使数据具有响应式 const { counter } = storeToRefs(store); // 4.点击 + 1; function handleClick() { // ref数据这里需要加.value访问 counter.value++; } </script>
❗️注意:
1) store 是什么?
通过 useStore() 获取到 store 是被reactive包装的包含store, getter, methods等内容的对象。
2) storeToRefs 的作用?
store 直接解构数据时,会是响应式丧失,需要通过storeToRefs, 保持结构后的变量保持ref响应式
修改 state
// 单个参数修改 state store.counter++ // 多个参数修改 state store.$patch({ counter: store.counter + 1, name: 'Abalam', }) //全部修改 state store.$state = { counter: 666, name: 'Paimon' } pinia.state.value = {} //重置State,将状态重置为初始值 const store = useStore() store.$reset()
❗️注意
1) $state、$patch、$reset
pinia.state.value = {}; //置空 store.$patch({counter: store.counter + 1, name: 'Abalam'})// 多个参数修改 state store.$state = { counter: 666, name: 'Paimon' }; //全部修改 state store.$reset(); //重置到初始状态
使用 mapstate
import { mapState } from 'pinia' import { useCounterStore } from '../stores/counterStore' export default { computed: { ...mapState(useCounterStore, ['counter']) // 或 ...mapState(useCounterStore, { myOwnName: 'counter', double: store => store.counter * 2, magicValue(store) { return store.someGetter + this.counter + this.double }, }), }, }
mapstate状态下的state是只读的,无法进行修改;如需修改需要使用mapWritableState
import { mapWritableState } from 'pinia' import { useCounterStore } from '../stores/counterStore' export default { computed: { // 允许访问组件内的 this.counter 并允许设置它 // this.counter++ // 与从 store.counter 读取相同 ...mapWritableState(useCounterStore, ['counter']) // 与上面相同,但将其注册为 this.myOwnName ...mapWritableState(useCounterStore, { myOwnName: 'counter', }), }, }
❗️提示:
对于像数组这样的集合,您不需要 mapWritableState(),除非您用 cartItems = [] 替换整个数组,mapState() 仍然允许您调用集合上的方法。
使用订阅
可以通过 store 的 subscribe()方法查看状态及其变化,类似于Vuex的subscribe方法。与常规的watch()相比,使用subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次;
const subscribe = store.$subscribe((mutation, state) => { /** * mutation主要包含三个属性值: * events:当前state改变的具体数据,包括改变前的值和改变后的值等等数据 * storeId:是当前store的id * type:用于记录这次数据变化是通过什么途径,主要有三个分别是 * “direct” :通过 action 变化的 ”patch object“ :通过 $patch 传递对象的方式改变的 “patch function” :通过 $patch 传递函数的方式改变的 * **/ // 我们就可以在此处监听store中值的变化,当变化为某个值的时候,去做一些业务操作之类的 console.log(mutation) console.log(state.baseUrl) if (state.baseUrl === afterChangeUrl) isShow.value = true else isShow.value = false }, { detached: false }) //第二个参数options对象,是各种配置参数 //detached:布尔值,默认是 false,正常情况下,当订阅所在的组件被卸载时,订阅将被停止删除, //如果设置detached值为 true 时,即使所在组件被卸载,订阅依然在生效 //参数还有immediate,deep,flush等等参数 和vue3 watch的参数是一样的,多的就不介绍了,用到再看文档吧 // 停止订阅 // subscribe() //调用上方声明的变量值,示例(subscribe),即可以停止订阅
2. getter
Getter 完全等同于 Store 状态的 计算值。 它们可以用 defineStore() 中的 getters 属性定义。 他们接收“state”作为第一个参数 ,在函数内可以使用this访问其他getter;
getter 中的值有缓存特性,类似于computed,如果值没有改变,多次使用也只会调用一次。
export const useStore = defineStore('main', { state: () => ({ counter: 0, }), getters: { doubleCount: (state) => state.counter * 2, // 自动推导返回类型 doubleCount(state) { return state.counter * 2 }, // 依赖getters返回参数,则需要显性的设置返回类型 doublePlusOne(): number { return this.doubleCount + 1 }, }, })
3. actions
不同于vuex,pinia的actions可以同步也可以异步,内部可以使用this访问整个store对象;
actions内的函数可以使用async标记。
export const useUserStore = defineStore({'user', actions: { increment() { this.counter++ }, increase() { // 调用另一个 action 的方法 this.increment() }, } })
可以使用 store.$onAction()
订阅 action 及其结果。 传递给它的回调在 action 之前执行。 after
处理 Promise 并允许您在 action 完成后执行函数。 以类似的方式,onError
允许您在处理中抛出错误。 这些对于在运行时跟踪错误很有用,类似于 Vue 文档中的这个提示。
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()
默认情况下,action subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 内)。 意思是,当组件被卸载时,它们将被自动删除。
export default { setup() { const someStore = useSomeStore() // 此订阅将在组件卸载后保留 someStore.$onAction(callback, true) // ... }, }
4.相关函数
☘️ mapState
import { mapState } from 'pinia' import { useCounterStore } from '../stores/counterStore' export default { computed: { // 允许访问组件内部的 this.counter // 与从 store.counter 读取相同 ...mapState(useCounterStore, { myOwnName: 'counter', // 您还可以编写一个访问 store 的函数 double: store => store.counter * 2, // 它可以正常读取“this”,但无法正常写入... magicValue(store) { return store.someGetter + this.counter + this.double }, }), }, }