pinia

198 阅读7分钟

什么是Pinia?

Pinia与Vuex一样,也是Vue的状态管理库。作为Vuex的代替者,Pinia后来居上,已经成为了开发者首选的状态管理工具。Pinia到底强在哪?

Pinia的优势有以下几点:

  • 同时支持Vue2和Vue3;
  • 简化了状态管理流程,Pinia抛弃了Mutation,这意味着你可以直接更新状态,不用再注册Commit;
  • 提供了更好的TypeScript支持,语法上更加贴近Composition Api

如何使用?

以一个新的Vue3项目为例,您可以通过以下命令,初始化一个项目,安装时关于Pinia选项选择Yes即可。

npm init vue

在项目的 src/stores 目录下,新建一个 index.ts 文件作为Pinia的入口文件,并创建根Store

import { createPinia } from "pinia"const store = createPinia()
​
export default store

在 main.ts 中引入使用即可

import store from './stores'
​
app.use(store)

这样写的目的是按照Vue2中的Vuex代码风格来的,你可以根据官方文档或者自己喜欢的风格来。当然我本人比较推荐单独创建一个入口文件,这样写有几个好处:

  • 保证main.ts里的代码整洁
  • 后期可能会使用一些Pinia插件,将这一部分内容写在一起有利于后期维护
  • 符合模块化开发的思想

在引入store之后,可以在 src/stores 目录下新建一个 modules 文件夹用于定义Sore。这样我们的pinia就可以投入使用了。

创建一个Store

我们在开发中需要根据模块划分状态,这样才方便我们统一管理不同模块下的状态。比如我们开发中会设计到用户模块,因此我们可以在 src/stores/modules 目录下新建一个 user.ts 文件用于用户状态管理,将通过下列代码创建user Store

import { defineStore } from 'pinia'// 定义状态的类型 一般情况下Pinia会自动推断出state的类型,你就无需定义接口。如果state的数据类型比较复杂,推荐你定义一个接口来描述state的类型
interface IUserState {
    firstName: string
    lastName: string
}
​
const useUserStore = defineStore('user', {
    state: (): IUserState => ({
        firstName: 'Liu',
        lastName: 'Dehua'
    }),
    getters: {
        fullName(): string {
            return this.firstName + ' ' + this.lastName
        }
    }
})
​
export default useUserStore

在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。

还有一种方式定义Store,就是以函数的形式。

import { defineStore } from 'pinia'
import { reactive, toRefs } from "vue"// 定义状态的类型 一般情况下Pinia会自动推断出state的类型,你就无需定义接口。如果state的数据类型比较复杂,推荐你定义一个接口来描述state的类型
interface IUserState {
    firstName: string
    lastName: string
}
​
const useUserStore = defineStore('user', () => {
    const state = reactive<IUserState>({
        firstName: 'Liu',
        lastName: 'Dehua'
    })
    
    const fullName = (): string => state.firstName + ' ' + state.lastName
    
    return {
        ...toRefs(state),
        fullName
    }
})
​
export default useUserStore

我个人比较推荐这种写法,因为这种写法在语法上更符合Composition Api。在这种方式下,你需要使用ref或reactive来定义状态,使用函数来定义getters和actions,最后将需要的内容return出去即可。在vue3.2之前,我们在组件中使用setup()的时候,是不是也将定义的数据return出去才能使用呢。既然要使用组合式Api,那就贯彻到底了~

对于这两种方式,在使用的时候是一样的。因为我们定义的Store是一个函数,所以在组件中需要调用后才能使用哦~

<template>
  <div class="about">
    <div class="store">
      <h1>在组件中您可以这样使用定义的Store</h1>
      <h2>FirstName:{{ userStore.firstName }}</h2>
      <h2>LastName:{{ userStore.lastName }}</h2>
      <h2>FullName:{{ userStore.fullName() }}</h2>
    </div>
  </div>
</template><script setup lang="ts">
import useUserStore from '@/stores/modules/user'const userStore = useUserStore()
</script>

image.png

大功告成啦

这样一个Store就大功告成啦!还有几点需要注意:

  1. 定义的文件名尽量符合语义化标准,让人看一眼这个文件就能知道这个文件管理的是什么模块;
  2. 定义store名称一般是useXxxxStore,这里的Xxxx就是文件名,让人看一眼这个代码就知道store管理的是什么模块。至于use是将这个store当做一个hook来使用,所以一般会加上use;
  3. 调用store获取状态时一般会去掉use,即 const userStore = useUserStore()

这样的话在代码中你的同事只需要瞄一眼就能明白含义了。(虽然这样写你可能不会得到同事的夸赞,但不这样写你一定会收获同事的祖安式问候)

怎么样,Pinia是不是很简单?看到这里,你就已经能基本使用Pinia进行状态管理啦~

状态持久化

用过vuex的都知道,有时我们会遇到需要将一些状态进行持久化处理,避免刷新浏览器的时候出现数据丢失。当然,你可以选择手动持久化,即手动的将需要持久化存储的数据添加到localStorage或sessionStore中,在定义state的时候默认从这两个Storage中拿。对于小部分数据来说可行,但如果需要大量持久化状态,这种方式就不是很推荐了。要一个个找不说,代码也会大量重复。

因此,我们这时就要借助插件来完成这个操作了。

安装插件: npm i pinia-plugin-persistedstate

在 src/stores/index.ts 中引入插件

import { createPinia } from "pinia"
// 引入持久化插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const store = createPinia()
// 将插件提供给store实例
store.use(piniaPluginPersistedstate)
​
export default store

在需要持久化的store中引入即可。因为我们定义store有两种方式,因此持久化也有对应的书写方式:

  • 第一种方式
import { defineStore } from 'pinia'// 定义状态的类型 一般情况下Pinia会自动推断出state的类型,你就无需定义接口。如果state的数据类型比较复杂,推荐你定义一个接口来描述state的类型
interface IUserState {
    firstName: string
    lastName: string
}
​
const useUserStore = defineStore('user', {
    state: (): IUserState => ({
        firstName: 'Liu',
        lastName: 'Dehua'
    }),
    getters: {
        fullName(): string {
            return this.firstName + ' ' + this.lastName
        }
    },
    actions: {
        updateLastName(lastName: string) {
            this.lastName = lastName
        }
    },
    // 表示这个store里的数据都将持久化存储
    persist: true
})
​
export default useUserStore

在函数式Store中是这样的:

import { defineStore } from 'pinia'
import { reactive, toRefs } from "vue"// 定义状态的类型 一般情况下Pinia会自动推断出state的类型,你就无需定义接口。如果state的数据类型比较复杂,推荐你定义一个接口来描述state的类型
interface IUserState {
    firstName: string
    lastName: string
}
​
const useUserStore = defineStore('user', () => {
    const state = reactive<IUserState>({
        firstName: 'Liu',
        lastName: 'Dehua'
    })
    
    const fullName = (): string => state.firstName + ' ' + state.lastName
    const updateLastName = (lastName: string) => state.lastName = lastName
    
    return {
        ...toRefs(state),
        fullName,
        updateLastName
    }
}, {
    // 注意defineStore的第三个参数可以传入插件配置
    persist: true
})
​
export default useUserStore

我们点击按钮,将lastName改成Xueyou后,发现已经被默认存储在了localStorage中。

image.png

默认存储在了localStorage

那么现在又出现了一个问题:并不是所有的状态都需要持久化呀,只持久化部分数据该怎么办呢?现在就需要对插件进行一个配置了。

在 src/utils 目录下新建 persist.ts 文件,修改存储配置:

import type { PersistedStateOptions } from 'pinia-plugin-persistedstate'

/**
 * @description pinia持久化参数配置
 * @param {String} key 存储到持久化的 name
 * @param {Array} paths 需要持久化的 state name
 * @return persist
 * */
const piniaPersistConfig = (key: string, paths?: string[]) => {
	const persist: PersistedStateOptions = {
		key,
		storage: window.localStorage,
		// storage: window.sessionStorage,
		paths
	}
	return persist
}

export default piniaPersistConfig

观察代码可知,我们不仅可以存储部分状态,并且可以选择存储方式~

因此修改store中的持久化配置:

import { defineStore } from 'pinia'
import { reactive, toRefs } from "vue"
import piniaPersistConfig from "@/utils/persist"

// 定义状态的类型 一般情况下Pinia会自动推断出state的类型,你就无需定义接口。如果state的数据类型比较复杂,推荐你定义一个接口来描述state的类型
interface IUserState {
    firstName: string
  	lastName: string
}

const useUserStore = defineStore('user', () => {
    const state = reactive<IUserState>({
        firstName: 'Liu',
        lastName: 'Dehua'
    })
    
    const fullName = (): string => state.firstName + ' ' + state.lastName
    const updateLastName = (lastName: string) => state.lastName = lastName
    
    return {
        ...toRefs(state),
    	fullName,
        updateLastName
    }
}, {
    // 注意defineStore的第三个参数可以传入插件配置,根据配置只持久化lastName
    persist: piniaPersistConfig('user', ['lastName'])
})

export default useUserStore

image.png

只有'lastName'被持久化缓存了下来

从图片中我们可以看到,只有'lastName'被持久化缓存了下来。