pinia全局状态管理工具
我们可以看到在Vue3中,官方已经用Pinia代替了Vuex成为官方的状态管理库。pinia可以更好的支持ts的使用
pinia官网链接Pinia 中文文档
特点:
- 支持Vue3 和 Vue2
- 去除 mutations,只有 state,getters,actions;
- actions 支持同步和异步;
- 完整的 ts 的支持;
- 无需再创建各个模块嵌套了,Vuex中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响。
- 足够轻量,压缩后的体积只有1kb左右;
- 无需手动添加 store,store 一旦创建便会自动添加;
- pinia支持插件来扩展自身功能。
- 支持服务端渲染。
模块化
在大型项目实际开发中,不可能把多个模块的数据都定义到一个store中,而是一个模块对应一个store,最后通过一个根store进行整合,结构目录如下:
module文件夹用于存放每个不同模块的状态管理
index.js为store的入口文件,将每个模块导入并统一导出
1. 安装
yarn add pinia
# 或者使用 npm
npm install pinia
2. 引入注册Vue3
import { createApp } from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
const store = createPinia()
let app = createApp(App)
app.use(store)
app.mount('#app')
vue2使用
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
// other options...
// ...
// note the same `pinia` instance can be used across multiple Vue apps on
// the same page
pinia,
})
3. 创建store
store简单来说就是数据仓库的意思,我们数据都放在store里面。当然你也可以把它理解为一个公共组件,只不过该公共组件只存放数据,这些数据我们其它所有的组件都能够访问且可以修改
在src下面创建一个store文件夹,再创建与之对应的js文件,比如user.js
import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useUser = defineStore('user', {
// other options...
})
Store 是使用 defineStore() 定义的,接收两个参数:
- name:一个字符串,必传项,该store的唯一id。
- options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
我们可以定义任意数量的store,因为我们其实一个store就是一个函数,这也是pinia的好处之一,让我们的代码扁平化了,这和Vue3的实现思想是一样的。
4. 使用 store
<script setup>
import { useStore } from '@/store/user.js'
const store = useStore()
console.log(store)
</script>
这是空store的输出内容,我们可以尝试依次添加state、getters、actions属性后观察store打印的内容
5. 添加state
import { defineStore } from 'pinia'
export const useStore = defineStore('user', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
name: '',
token: ''
}
},
})
添加state属性之后,可以看到,state里面return的属性添加到了store上,这时我们在获取时不需要在state上获取,而是直接在store上获取
请注意,store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构:
5.1. 读取state
<script setup>
import { useStore } from '@/store/user.js'
const store = useUser()
console.log(store)
// ❌ 这不起作用,因为它会破坏响应式
// 这和从 props 解构是一样的
const { name } = store ❌
// 需要调用storeToRefs方法
const { uname } = storeToRefs(store)
</script>
5.2. 重置状态
const store = useStore()
store.$reset()
5.3. 修改state
store.name='name'
5.4. 批量修改state
调用 $patch 方法。 它允许您使用部分“state”对象同时应用多个更改:
store.$patch({
counter: store.counter + 1,
name: 'Abalam',
})
但是,使用这种语法应用某些突变非常困难或代价高昂:任何集合修改(例如,从数组中推送、删除、拼接元素)都需要您创建一个新集合。 正因为如此,$patch 方法也接受一个函数来批量修改集合内部分对象的情况:
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
5.5. 直接替换整个state(几乎不用)
// 替换
store.$state = { userName: 'rose' }
6. getter
6.1. 使用getter
类似于计算属性,他们接收“状态”作为第一个参数以鼓励箭头函数的使用:
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
})
获取
// 直接获取
<div>{{userStore.doubleCount}}</div>
6.2. getters方法中带参数
export const useStore = defineStore('main', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
调用
// 直接获取
<div>{{userStore.getUserById(1)}}</div>
6.3. 访问其他 getter
通过 this 访问到 整个 store 的实例
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
// 类型是自动推断的,因为我们没有使用 `this`
doubleCount: (state) => state.counter * 2,
// 这里需要我们自己添加类型(在 JS 中使用 JSDoc)。 我们还可以
// 使用它来记录 getter
/**
* 返回计数器值乘以二加一。
*
* @returns {number}
*/
doubleCountPlusOne() {
// 自动完成 ✨
return this.doubleCount + 1
},
},
})
6.4. 访问其他Store中的 getter
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
使用setup()
import { useCounterStore } from '../stores/counterStore'
export default {
setup() {
const counterStore = useCounterStore()
return { counterStore }
},
computed: {
quadrupleCounter() {
return counterStore.doubleCounter * 2
},
},
}
没有setup()
您可以使用 previous section of state 中使用的相同 mapState() 函数映射到 getter:
import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'
export default {
computed: {
// 允许访问组件内的 this.doubleCounter
// 与从 store.doubleCounter 中读取相同
...mapState(useCounterStore, ['doubleCount'])
// 与上面相同,但将其注册为 this.myOwnName
...mapState(useCounterStore, {
myOwnName: 'doubleCounter',
// 您还可以编写一个访问 store 的函数
double: store => store.doubleCount,
}),
},
}
7. Actions
前面我们提到的state和getters属性都主要是数据层面的,并没有具体的业务逻辑代码,它们两个就和我们组件代码中的data数据和computed计算属性一样。
那么,如果我们有业务代码的话,最好就是写在actions属性里面,该属性就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。
actions属性值同样是一个对象,该对象里面也是存储的各种各样的方法,包括同步方法和异步方法。
7.1. 添加actions(此时的useUsersStore即是最基本的一个架构了)
import { defineStore } from 'pinia'
export const useUsersStore = defineStore('users', {
// 其它配置
state: () => {
return {
name: "李华",
age: 25,
sex: "男",
}
},
getters: {
getAddAge: (state) => {
return state.age + 100;
},
getAddParamsAge: (state) => {
return (params:number)=>state.age + 100 + params;
},
getNameAndAge(): string {
return this.name + this.getAddAge;
},
},
actions: {
saveName(name: string) {
// 修改state中的name
this.name = name;
},
},
})
此处actions里常写复杂逻辑方法。举例仅为简单的直接修改了state的值。在实际场景中,该方法可以是任何逻辑,比如发送请求、存储token等等。大家把actions方法当作一个普通的方法即可,特殊之处在于该方法内部的this指向的是当前store。
actions定义的方法同样在store直接可以访问到。