相关
官方文档:https://pinia.vuejs.org/ 中文文档:https://pinia.web3doc.top/
Pinia
1. 介绍
- 足够轻量,Pinia 重约 1kb,甚至会忘记它的存在!
- 去除 Mutation ,Actions 支持同步和异步(Actions一个顶俩,写起来简洁);
- 无需手动注册 Store,Store 仅需要时才自动注册。如果从不使用,则永远不会“注册”(省心);
- 没有模块嵌套,只有 Store 的概念,Store 之间可以自由使用,更好的代码分割;
- Vue2 和 Vue3 都能支持;
- 支持大型项目迁移期间,Pinia 和 Vuex 混合使用(贴心迁移);
- 更完美的 typescript 的支持;
- 与 Vue devtools 挂钩,Vue2 和 Vue3 开发体验更好;
- 支持插件扩展功能;
- 支持模块热更新,无需加载页面可以修改容器,可以保持任何现有的状态;
- 支持服务端渲染;
2. 安装
代码语言:javascript复制npm install pinia
提示
如果您的应用使用 Vue 2,您还需要安装组合 API:@vue/composition-api。
2.1 Vue3.x版本使用
代码语言:javascript复制import { createPinia } from 'pinia'
app.use(createPinia())
2.2 Vue2.x版本使用
代码语言:javascript复制import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
// 其他选项...
// ...
// 注意同一个 `pinia` 实例可以在多个 Vue 应用程序中使用
// 同一个页面
pinia,
})
使用Pinia
1. store
一个 Store (如 Pinia)是一个实体,它持有未绑定到您的组件树的状态和业务逻辑。换句话说,它托管全局状态。它有点像一个始终存在并且每个人都可以读取和写入的组件。它有三个概念,state、getters 和 actions 并且可以安全地假设这些概念等同于组件中的“数据”、“计算”和“方法”。
提示
Pinia 的目录通常被称为 stores 而不是 store, 这是为了强调 Pinia 使用多个 store,而不是 Vuex 中的单个 store,同时也有迁移期间 Pinia 和 Vuex 混合使用的考虑。
1.1 创建store
代码语言:javascript复制// 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: {}
})
1.2 setup内使用store
代码语言:javascript复制<template>
<div>
<button @click="handleClick">修改状态数据</button>
<!-- 模板内不需要加.value -->
<p>{{store.name}}</p>
<!-- computed获取 -->
<p>{{name}}</p>
<!-- 或者使用解构之后的 -->
<p>{{counter}}</p>
</div>
</template>
<script setup>
import { useStore } from '@/stores/index.ts'
// 使普通数据变响应式的函数
import { storeToRefs } from "pinia";
const store = useStore()
// 结合computed获取
const name = computed(() => store.name)
// 解构并使数据具有响应式
const { counter } = storeToRefs(store);
// 点击 1;
function handleClick() {
// ref数据这里需要加.value访问
counter.value ;
}
</script>
1.3 修改state
代码语言:javascript复制// 单个参数修改 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.3.1 $patch、$reset
代码语言:javascript复制pinia.state.value = {}; //置空
store.$state = { counter: 666, name: 'Paimon' }; //全部修改 state
store.$reset(); //重置到初始状态
1.4 mapstate
代码语言:javascript复制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
代码语言:javascript复制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() 仍然允许您调用集合上的方法。
1.5 订阅
可以通过 store 的 subscribe() 方法查看状态及其变化,类似于 Vuex 的 subscribe 方法。 与常规的 watch() 相比,使用 subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次;
代码语言:javascript复制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,如果值没有改变,多次使用也只会调用一次。
代码语言:javascript复制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标记。
代码语言:javascript复制 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() 内)。 意思是,当组件被卸载时,它们将被自动删除。
代码语言:javascript复制export default {
setup() {
const someStore = useSomeStore()
// 此订阅将在组件卸载后保留
someStore.$onAction(callback, true)
// ...
},
}
4.相关函数
4.1 mapState
代码语言:javascript复制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
},
}),
},
}