可爱简约又轻量的Pinia,你确定不用它吗?

开发 前端
Pinia.js是由Vue.js团队核心成员开发的新一代状态管理器,使用Composition Api进行重新设计的,也被视为下一代Vuex。Pinia是一个Vue的状态管理库。

1 写在前面

Pinia.js是由Vue.js团队核心成员开发的新一代状态管理器,使用Composition Api进行重新设计的,也被视为下一代Vuex。Pinia是一个Vue的状态管理库,允许跨组件、跨页面进行全局共享状态,也由于其设计的简洁性、和对typescript的良好支持,取代Vuex指日可待。

或许,你在想在vue3中Composition API完全可以使用响应式变量进行全局共享状态,为什么还需要Pinia呢?其实你忽略的一点,你这样做在单页面进行应用是完全可以的,但是如果页面时服务端进行渲染呈现的,那么就会出现安全漏洞。

Pinia的优点有:

  • 完全支持Typescript,不需要进行复杂的配置
  • 移除了“天下苦秦久矣”的mutations,不需要再区分同步异步去使用不同的方法,actions同时支持同步和异步,这样形成了state、getters、actions三剑客格局
  • 支持服务端渲染
  • 不再需要套娃🪆了,没有模块嵌套,只有简简单单可以自由使用的store概念,形成了更好的代码分割体验
  • 相比于Vuex更加轻量的设计,压缩代码打包的体积

总而言之,Pinia不仅可爱,还简单轻量易上手。

2 开始使用

安装Pinia

$ yarn add pinia
$ npm i pinia
$ pnpm i pinia

创建Store

根据Pinia官方给的格式建议,我们可以在src目录下创建store目录,再在其下创建index.ts文件作为store的全局配置文件,并导出store,方便全局挂载。


//src/store/index.ts
import {App} from "vue"
import {createPinia} from "pinia"

const store = createPinia()

export function setupStore(app:App<Element>){
app.use(store)
}

export {store}

在main.ts文件中进行全局挂载store文件:

//src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import {setupStore} from "@stores/index"

const app = createApp(App);
// 挂载store
setupStore(app)
app.mount("#app")

简单示例

先定义一个counter的store状态管理文件。

//src/store/modules/counter.ts
import {defineStore} from "pinia"

export const useCounterStore = defineStore("counter",{
state:()=>{
return {
count:0
}
},
actions:{
increment(){
this.count++
}
}
})

而后在vue文件进行使用:

<template>
<div>count:{{counter.count}}</div>
</template>
<script lang="ts" setup>
import { useCounterStore } from '@store/modules/counter';

const counter = useCounterStore();
counter.count++
counter.$patch({count:counter.count})
counter.increment()
</script>

显示结果

3State 数据状态

定义state状态

在使用仓库状态数据前,你需要先使用defineStore()进行定义一个仓库,需要使用唯一的名字进行命名。

//src/store/modules/counter.ts
import {defineStore} from "pinia"

export const useCounterStore = defineStore("counter",{
state:()=>{
return {
count:0
}
}
})

使用state状态

在pinia中,state是使用函数进行定义,并返回初始化数据,这样可以允许pinia作为服务端和客户端进行使用。

我们已经定义了一个store仓库,使用useStore()进行获取仓库state数据。

<template>
<div>count:{{count}}</div>
<div>doubleCount:{{doubleCount}}</div>
</template>
<script lang="ts" setup>
import { useCounterStore } from '@store/modules/counter';
const counter = useCounterStore();
//使用computed获取响应式数据
const doubleCount = computed(()=>counter.count*2);

const {count} = counter;
</script>

需要注意的是,在导入store使用state状态时,直接进行解构会使得变量失去响应式。上面的数据中,count变量是失去了响应式的,它的值一直恒定不变。对此,应该像composition API使用props一样,从pinia中导出storeToRefs进行获取响应式数据。

const counter = useCounterStore();
const {count} = storeToRefs(counter)

对此,count现在转为响应式的数据了,这样将会把store中的state变量成了响应式,storeToRefs将会自动跳过所有的actions和不需要进行响应式处理的属性。

修改state状态

通常,你可以通过store实例进行直接获取和修改state数据,可以使用store.$reset()进行重置state的数据回复到初始值。

import { useCounterStore } from '@stores/modules/counter';
const counter = useCounterStore();
counter.count++;

当然,你还可以通过使用pinia的内置API即$patch()进行更新,它允许你在同一时间进行多个数据的改变。$patch()允许你去组合多个改变的值,可以像数组进行增删改查。

import { useTodosStore } from "@stores/modules/todos";
const todosStore = useTodosStore()
todosStore.$patch(state=>{
state.todos.push({
name:"yichuan",
age:18
})
state.isTrue = true
})

$patch()允许你去组合多个改变的值,可以像数组进行增删改查。

替换state的值,可以通过设置$state的值进行替换store中的state值。

store.$state = {counter:666, name:"onechuan"}

当然也可以通过pinia的实例去改变整个state的值。

pinia.state.value= {}

但是,一般不建议直接修改state的值,Pinia中建议使用actions方法去修改和管理state的值。

监听state状态变化

订阅state的值:你可以通过store的$subscribe()方法去观察state的改变,类似于subscribe方法。与常规watch()相比,使用$subscribe()的优势在于,在补丁发布后,订阅只会触发一次。

numerStore.$subscribe((mutation,state)=>{
mutation.counter
mutation.name
mutation.isAdmin
localStorage.setItem("numer",JSON.stringify(state))
})

默认情况下,状态订阅被绑定到添加它们的组件上(如果存储在组件的setup()中)。这就以为着当组件被卸载的时候,将自动移除。如果你想再组件被移除后继续保持它们,可以通过设置{detached:true}作为第二个参数来从当前组件中分离状态订阅。

const someStore = useSomeStore()
someStore.$subscribe(callback, { detached: true })

4Getters

getters与store状态的computed的值完全相同,可以通过defineStore()中的getters属性来定义,它们通过接收state作为箭头函数的第一个参数。

export const useStore = defineStore('counter', {
state: () => ({
counter: 0,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
})

绝大多数情况,getters将只依赖于state,然而,它们也有可能需要去使用其它的getters。因此,在定义一个普通函数时,我们可以通过这个函数访问整个store实例,但需要定义返回类型的类型。由于ts中的已知的限制,并不影响使用箭头函数定义的getter和不使用this。

import {defineStore} from "pinia"

export const useNumerStore = defineStore("numer",{
state:()=>({
counter:0,
name:"numer",
isAdmin:true
}),
getters:{
doubleCount(state){
return state.counter * 2
},
// 当使用this的时候,必须准确地设置返回值的类型
doublePlusOne():number{
return this.counter * 2 + 1
}
}
})

当然你也可以通过计算属性去获取多个getters,需要通过this去获取任意的其他getter。

export const useStore = defineStore("main",{
state:()=>({
counter:0
}),
getters:{
doubleCount:state=>state.counter * 2,
doubleCountPlusOne(){
return this.doubleCount + 1
}
}
})

getter只是在幕后的计算属性,因此不能向其传递任何参数。但是你可以从getter返回一个函数来接收任何参数。

export const useStore = defineStore('main', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})

在组件使用:

<div>用户:{{getUserById(1)}}</div>

const numerStore = useNumerStore()
const {getUserById} = numerStore

注意,当这样做时,getter不再被缓存,它们只是您调用的函数。不过,您可以在getter本身中缓存一些结果,这并不常见,但应该会证明更高效.

export const useStore = defineStore('main', {
getters: {
getActiveUserById(state) {
const activeUsers = state.users.filter((user) => user.active)
return (userId) => activeUsers.find((user) => user.id === userId)
},
},
})

获取其它store的getters,要使用另一个store getter,你可以直接在getter中使用它,其实和在vue文件中使用差别不大。

import { useOtherStore } from './other-store'

export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})

5Actions

actions等价于组件中的方法,它们可以在defineStore()中进行定义actions属性,并且可以完美地去定义业务逻辑。

export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})

在上面的代码中,我们可以看到actions有点类似getters,但事实上是有所不同的。

  • 相同点:actions和getters都可以全类型支持来访问整个store实例。
  • 不同点:actions操作可以是异步的,可以在其中等待任何api调用甚至其他操作。

注意,只要你得到了一个Promise,你使用的库并不重要,你甚至可以使用本地的fetch函数(仅适用于浏览器):

import { mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
state: () => ({
userData: null,
// ...
}),

actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// let the form component display the error
return error
}
},
},
})

同样的,action也可以像state和getters进行相互使用,action可以通过this直接访问。

// src/store/user.ts
export const useUserStore = defineStore({
"user",
state: () => ({
userData: null
}),
actions:{
async login(account, pwd) {
const { data } = await api.login(account, pwd)
this.setData(data) // 调用另一个 action 的方法
return data
},
setData(data) {
this.userData = data
}
}
})

也可以在action 里调用其他 store 里的 action,引入对应的 store 后即可访问其内部的方法了。

// src/store/user.ts

import { useAppStore } from './app'
export const useUserStore = defineStore({
id: 'user',
actions: {
async login(account, pwd) {
const { data } = await api.login(account, pwd)
const appStore = useAppStore()
appStore.setData(data) // 调用 app store 里的 action 方法
return data
}
}
})
// src/store/app.ts
export const useAppStore = defineStore({
"app",
state:()=>{
userData: null
},
actions: {
setData(data) {
this.userData = data
}
}
})

6 参考文章

《Pinia官方文档》

《新一代状态管理工具,Pinia.js 上手指南》

7 写在最后

本篇文章是在阅读文档和相关文章进行学习总结得来,是在公司进行应用生产前的实验,对于能力强的大佬应该是小意思,从vuex过渡可以直接上手。Pinia是很好的状态管理,简洁轻量易上手。在进行总结文章的时候,也做了一些代码实践,但是不免会出现一些纰漏。

责任编辑:姜华 来源: 前端万有引力
相关推荐

2020-05-25 19:44:58

LubuntuLubuntu 20.

2021-06-08 11:15:10

Redis数据库命令

2018-04-17 11:47:06

if代码参数

2023-06-27 08:41:35

DapperSQL语句

2021-07-16 22:49:50

PiniaVuex替代品

2020-08-25 18:52:22

人工智能机器人技术

2024-01-08 08:23:08

OpenCV机器学习计算机视觉

2022-04-15 14:31:02

鸿蒙操作系统

2020-10-16 09:40:18

顺序Spring AOPHTTP

2023-11-23 10:21:37

2022-02-08 11:45:03

PiniaVuex前端

2021-01-08 09:44:23

Faceboo隐私数据安全

2024-04-19 13:17:40

PostgreSQLSQL数据库

2023-02-27 09:03:23

JavaCAS

2024-03-14 11:06:37

JavaScript引擎探索

2021-08-26 05:27:57

Swift 监听系统泛型

2018-03-28 14:37:43

商务办公

2024-01-05 15:32:47

鸿蒙SNTP智慧时钟

2012-03-01 11:20:45

2020-03-10 08:01:05

Java堆内存线程共享
点赞
收藏

51CTO技术栈公众号