简介和优势
Pinia是Vue生态里Vuex的代替者,一个全新Vue的状态管理库。pinia的开发团队,就是Vuex的开发团队,在Vue3成为正式版以后,是尤雨溪强势推荐的项目。
- 可以对Vue2和Vue3做到很好的支持,也就是老项目也可以使用Pinia。
- 抛弃了Mutations的操作,只有state、getters和actions.极大的简化了状态管理库的使用,让代码编写更加容易直观。
- 不需要嵌套模块,符合Vue3的Composition api ,让代码更加扁平化。
- 完整的TypeScript支持。Vue3版本的一大优势就是对TypeScript的支持,所以Pinia也做到了完整的支持。而Vuex对TS的语法支持不是完整的。
- 代码更加简洁,可以实现很好的代码自动分割。Vue2的时代,写代码需要来回翻滚屏幕屏幕找变量,非常的麻烦,Vue3的Composition api完美了解决这个问题。 可以实现代码自动分割,pinia也同样继承了这个优点。
简单总结Pinia的优势就是,更加简洁的语法,完美支持Vue3的Composition api 和 对TypesCcript的完美支持。
基本用法
安装
npm install pinia
# or with yarn
yarn add pinia
在 main.js 中 引入 Pinia
// src/main.js
import { createPinia } from 'pinia'
const pinia = createPinia()
const app =createApp(App)
app.use(pinia)
定义一个 Store
在 src/stores 目录下创建 counter.js 文件,使用 defineStore() 定义一个 Store 。
- defineStore() 第一个参数是 storeId ,第二个参数是一个选项对象
- state 属性:用来存储全局的状态的,这里边定义的,就可以是为SPA里全局的状态了。
- getters属性:用来监视或者说是计算状态的变化的,有缓存的功能。
- actions属性:对state里数据变化的业务逻辑,需求不同,编写逻辑不同。说白了就是修改state全局状态数据的。
// src/store/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ name: '小红', age: 1 }),
getters: {},
actions: {
increment() {
this.age++
}
}
})
在组件中使用
在组件中导入刚才定义的函数,并执行一下这个函数,就可以获取到 store 了:
<template>
<div class="content">
<div>{{ userStore.name }}</div>
<div>{{ userStore.age }}</div>
</div>
</template>
<script setup>
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
</script>
State
状态数据的改变
这种改变状态数据的方法是非常方便的,要比Vuex
简洁太多了。
<template>
<div class="content">
<div>{{ userStore.name }}</div>
<div>{{ userStore.age }}</div>
<button @click="handleClick">增加</button>
</div>
</template>
<script setup>
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
const handleClick = () => {
userStore.age++
}
</script>
解构的坑
store 是一个用 reactive 包裹的对象,如果直接解构会失去响应性。我们可以使用 storeToRefs() 对其进行解构:
<template>
<div class="content">
<div>{{ name }}</div>
<div>{{ age }}</div>
<button @click="handleClick">增加</button>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
const { name, age } = storeToRefs(userStore)
const handleClick = () => {
userStore.age++
}
</script>
修改状态数据的多种方式
除了可以直接用 store.age++ 来修改 store,我们还可以调用 $patch
方法进行修改。官网明确表示$patch
的方式是经过优化的,会加快修改速度,对程序的性能有很大的好处并且可以同时修改多个状态。
<template>
<div class="content">
<div>{{ name }}</div>
<div>{{ age }}</div>
<div>{{ userStore.name }}</div>
<div>{{ userStore.age }}</div>
<button @click="handleClick">增加</button>
<button @click="handleClickPatch">patch</button>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
const { name, age } = storeToRefs(userStore)
const handleClick = () => {
userStore.age++
}
const handleClickPatch = () => {
userStore.$patch({
age: userStore.age + 2
})
}
</script>
但是,这种方法修改集合(比如从数组中添加、删除、插入元素)都需要创建一个新的集合,代价太高。因此,$patch
方法也接受一个函数来批量修改:
userStore.$patch(state => {
state.age = state.age * 2
})
在actions中写好逻辑 再调用actions
如果你有一个修改的过程非常复杂,你可以先在store
里,定义好actions
中的函数,然后在组件里再调用函数。
actions: {
changeAge() {
this.age = this.age * 5
}
},
有了这个changeAge( )
函数后,就可以在组件中调用这个函数来修改状态数据了。
const handleClickActions = () => {
userStore.changeAge()
}
注意:在用
actions
的时候,不能使用箭头函数,因为箭头函数绑定是外部的this。
Getters
访问 store 实例
大多数情况下,getter 只会依赖 state 状态。但有时候,它会使用到其他的 getter ,这时候我们可以通过 this 访问到当前 store 实例。
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ name: '小红', age: 1, phone: '15511112222' }),
getters: {
phoneHidden(state) {
return state.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
}
},
actions: {
changeAge() {
this.age = this.age * 5
}
}
})
Getters的缓存特性
Getters
是有缓存特性的
getters:{
phoneHidden(state){
console.log('PhoneHidden被调用了');
return state.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
}
}
// 使用this关键字
phoneHidden(){
return this.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
}
然后回到浏览器中按F12
打开查看Console
面板,可以看到只显示了一次PhoneHidden被调用了
,也变相说明了getters
是有缓存的,虽然调用多次,但是值一样就不会被多次调用。
新写一个方法handleClickChangePhone
。用来改变电话号码。
// 点击按钮的对应函数
const handleClickChangePhone = () => {
store.phone = "15139333999";
};
有了函数后,再编写一个按钮,触发这个函数,电话号码就变化了。
当电话号码改变时,Getters会自动工作,对应的phoneHidden
方法也会随着调用一次,清除以前的数据缓存。
store的互相调用
以使用另一个store中的state为例,getters、actions其实是一样的。
- 创建friends store
// src/store/friends.js
import { defineStore } from 'pinia'
export const useFriendsStore = defineStore('friends', {
state: () => ({ list: ['小明', '小灰', '小黑'] }),
getters: {},
actions: {}
})
- 在user中使用
// src/store/user.js
import { defineStore } from 'pinia'
import { useFriendsStore } from './friends'
export const useUserStore = defineStore('user', {
state: () => ({ name: '小红', age: 1, phone: '15511112222' }),
getters: {
phoneHidden(state) {
console.log('PhoneHidden被调用了')
return state.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
}
},
actions: {
changeAge() {
this.age = this.age * 5
},
getFriendsList() {
console.log(useFriendsStore().list)
}
}
})
Plugins
由于是底层 API,Pania Store 完全支持扩展。以下是可以扩展的功能列表:
- 向 Store 添加新状态
- 定义 Store 时添加新选项
- 为 Store 添加新方法
- 包装现有方法
- 更改甚至取消操作
- 实现本地存储等副作用
- 仅适用于特定 Store
使用方法
Pinia 插件是一个函数,接受一个可选参数 context ,context 包含四个属性:app 实例、pinia 实例、当前 store 和选项对象。函数也可以返回一个对象,对象的属性和方法会分别添加到 state 和 actions 中。
export function myPiniaPlugin(context) {
context.app // 使用 createApp() 创建的 app 实例(仅限 Vue 3)
context.pinia // 使用 createPinia() 创建的 pinia
context.store // 插件正在扩展的 store
context.options // 传入 defineStore() 的选项对象(第二个参数)
// ...
return {
hello: 'world', // 为 state 添加一个 hello 状态
changeHello() { // 为 actions 添加一个 changeHello 方法
this.hello = 'pinia'
}
}
}
然后使用 pinia.use() 将此函数传递给 pinia 就可以了:
// src/main.js
import { createPinia } from 'pinia'
const pinia = createPinia()
pinia.use(myPiniaPlugin)
实现本地存储
npm i pinia-plugin-persist
然后引入插件,并将此插件传递给 pinia :
// src/main.js
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'
const pinia = createPinia()
pinia.use(piniaPluginPersist)
接着在定义 store 时开启 persist 即可:
// src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 1 }),
// 开启数据缓存
persist: {
enabled: true
}
})
这样,无论怎么刷新,数据都不会丢失啦!
默认情况下,会以 storeId 作为 key 值,把 state 中的所有状态存储在 sessionStorage 中。我们也可以通过 strategies 进行修改:
// 开启数据缓存
persist: {
enabled: true,
strategies: [
{
key: 'myCounter', // 存储的 key 值,默认为 storeId
storage: localStorage, // 存储的位置,默认为 sessionStorage
paths: ['name', 'age'], // 需要存储的 state 状态,默认存储所有的状态
}
]
}
pinia模块化实现
假设我们目前store中有两个模块user.js和counter.js,我们再手动设置一个index.js
1.我们在index.js中导入user.js和counter.js,
2.并统一导出useStore方法,返回user和counter中的内容
import useCounterStore from './counter.js'
import useUserStore from './user.js'
export default function useStore(){
return {
user:useUserStore(),
counter:useCounterStore()
}
}
3.那么如何在组件中具体使用呢?
首先从store/index.js导入useStore方法
由于使用useStore方法会返回一个对象,对象中有user和counter,
我们使用解构赋值可以从useStore方法得到具体的某个模块
注意: import useStore from './store'
虽然没有在后面加上/index.js,但是会默认选中文件夹下的index.js
<script setup>
import useStore from './store'
const { counter } = useStore()
</script>
<template>
<div>主页组件</div>
<div>计数:{{ counter.count }}</div>
<div>计数加倍: {{ counter.double }}</div>
</template>
用vue-devtools对Pinia的调试
安装谷歌插件vue-devtools
按F12
进入调试模式,然后点击Vue
标签。
面板上默认显示是Components
,点击后,也可以选择Pinia
。这时候点击Pinia
就可以看到store里边的state、getters...
等信息了。
支持可视化调试
vue-devtools
是支持可视化的调试,调试后会直接把结果显示在页面上。
参考
pinia官方文档 pinia.vuejs.org/zh/