Pinia半小时快速上手

172 阅读3分钟

简介和优势

Pinia是Vue生态里Vuex的代替者,一个全新Vue的状态管理库。pinia的开发团队,就是Vuex的开发团队,在Vue3成为正式版以后,是尤雨溪强势推荐的项目。

  1. 可以对Vue2和Vue3做到很好的支持,也就是老项目也可以使用Pinia。
  2. 抛弃了Mutations的操作,只有state、getters和actions.极大的简化了状态管理库的使用,让代码编写更加容易直观。
  3. 不需要嵌套模块,符合Vue3的Composition api ,让代码更加扁平化。
  4. 完整的TypeScript支持。Vue3版本的一大优势就是对TypeScript的支持,所以Pinia也做到了完整的支持。而Vuex对TS的语法支持不是完整的。
  5. 代码更加简洁,可以实现很好的代码自动分割。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其实是一样的。

  1. 创建friends store
 // src/store/friends.js
 import { defineStore } from 'pinia'
 ​
 export const useFriendsStore = defineStore('friends', {
   state: () => ({ list: ['小明', '小灰', '小黑'] }),
   getters: {},
   actions: {}
 })
  1. 在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 插件是一个函数,接受一个可选参数 contextcontext 包含四个属性: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

image.png

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官方文档 pinia.vuejs.org/zh/