本文为稀土技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
前言
Pinia ,发音为/pinj/,来源于西班牙语pia 。意思为菠萝,表明与菠萝相同,由很多小块组成。在 Pinia 中,每个 Store 都是独自存在,一同进行状况办理。
Pinia 是由 Vue.js 团队成员开发,开始是为了探索 Vuex 下一次迭代会是什么样子。过程中,Pinia 完成了 Vuex5 提案的大部分内容,所以就取而代之了。
与 Vuex 比较,Pinia 提供了更简略的 API,更少的规范,以及 Composition-API 风格的 API 。更重要的是,与 TypeScript 一同运用具有牢靠的类型揣度支撑。
Pinia 与 Vuex 3.x/4.x 的不同
- mutations不复存在。只要 state 、getters 、actions。
- actions 中支撑同步和异步办法修正 state 状况。
- 与 TypeScript 一同运用具有牢靠的类型揣度支撑。
- 不再有模块嵌套,只要 Store 的概念,Store 之间能够相互调用。
- 支撑插件扩展,能够非常便利完成本地存储等功用。
- 愈加轻量,紧缩后体积只要 2kb 左右。
既然 Pinia 这么香,那么还等什么,一同用起来吧!
根本用法
装置
npm install pinia
在 main.js 中 引入 Pinia
// src/main.js
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
界说一个 Store
在 src/stores 目录下创立 counter.js 文件,运用 defineStore() 界说一个 Store 。defineStore() 第一个参数是 storeId ,第二个参数是一个选项目标:
// src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
咱们也能够运用更高档的办法,第二个参数传入一个函数来界说 Store :
// src/stores/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
在组件中运用
在组件中导入方才界说的函数,并执行一下这个函数,就能够获取到 store 了:
<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
// 以下三种方式都会被 devtools 跟踪
counterStore.count++
counterStore.$patch({ count: counterStore.count + 1 })
counterStore.increment()
</script>
<template>
<div>{{ counterStore.count }}</div>
<div>{{ counterStore.doubleCount }}</div>
</template>
这便是根本用法,下面咱们来介绍一下每个选项的功用,及插件的运用办法。
State
解构 store
store是一个用 reactive包裹的目标,如果直接解构会失掉呼应性。咱们能够运用 storeToRefs() 对其进行解构:
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
const { count, doubleCount } = storeToRefs(counterStore)
</script>
<template>
<div>{{ count }}</div>
<div>{{ doubleCount }}</div>
</template>
修正 store
除了能够直接用 store.count++ 来修正 store,咱们还能够调用 $patch
办法进行修正。$patch
性能更高,并且能够同时修正多个状况。
<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
counterStore.$patch({
count: counterStore.count + 1,
name: 'Abalam',
})
</script>
可是,这种办法修正集合(比如从数组中增加、删去、插入元素)都需要创立一个新的集合,代价太高。因而,$patch
办法也承受一个函数来批量修正:
cartStore.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
监听 store
咱们能够经过 $subscribe()
办法能够监听 store 状况的改变,类似于 Vuex 的 subscribe 办法。与 watch() 比较,运用 $subscribe()
的优点是,store 多个状况发生改变之后,回调函数只会执行一次。
<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
counterStore.$subscribe((mutation, state) => {
// 每逢状况发生改变时,将 state 持久化到本地存储
localStorage.setItem('counter', JSON.stringify(state))
})
</script>
也能够监听 pinia 实例上一切 store 的改变
// src/main.js
import { watch } from 'vue'
import { createPinia } from 'pinia'
const pinia = createPinia()
watch(
pinia.state,
(state) => {
// 每逢状况发生改变时,将一切 state 持久化到本地存储
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
Getters
拜访 store 实例
大多数情况下,getter 只会依靠 state 状况。但有时分,它会运用到其他的 getter ,这时分咱们能够经过this拜访到当时 store 实例。
// src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubleCount(state) {
return state.count * 2
},
doublePlusOne() {
return this.doubleCount + 1
}
}
})
拜访其他 Store 的 getter
要运用其他 Store 的 getter,能够直接在getter内部运用:
// src/stores/counter.js
import { defineStore } from 'pinia'
import { useOtherStore } from './otherStore'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 1
}),
getters: {
composeGetter(state) {
const otherStore = useOtherStore()
return state.count + otherStore.count
}
}
})
将参数传递给 getter
getter本质上是一个computed ,无法向它传递任何参数。可是,咱们能够让它回来一个函数以承受参数:
// src/stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
users: [{ id: 1, name: 'Tom'}, {id: 2, name: 'Jack'}]
}),
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
}
}
})
在组件中运用:
<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const { getUserById } = storeToRefs(userStore)
</script>
<template>
<p>User: {{ getUserById(2) }}</p>
</template>
留意:如果这样运用,getter 不会缓存,它只会当作一个一般函数运用。一般不引荐这种用法,由于在组件中界说一个函数,能够完成同样的功用。
Actions
拜访 store 实例
与 getters 相同,actions 能够经过 this 拜访当 store 的实例。不同的是,actions 能够是异步的。
// src/stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ userData: null }),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
} catch (error) {
return error
}
}
}
})
拜访其他 Store 的 action
要运用其他 Store 的 action,也能够直接在action内部运用:
// src/stores/setting.js
import { defineStore } from 'pinia'
import { useAuthStore } from './authStore'
export const useSettingStore = defineStore('setting', {
state: () => ({ preferences: null }),
actions: {
async fetchUserPreferences(preferences) {
const authStore = useAuthStore()
if (authStore.isAuthenticated()) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated!')
}
}
}
})
以上便是 Pinia 的具体用法,是不是比 Vuex 简略多了。除此之外,插件也是 Pinia 的一个亮点,个人觉得非常有用,下面咱们就来重点介绍一下。
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)
向 Store 增加新状况
能够简略地经过回来一个目标来为每个 store 增加状况:
pinia.use(() => ({ hello: 'world' }))
也能够直接在store上设置特点来增加状况,为了使它能够在 devtools 中运用,还需要对 store.$state 进行设置:
import { ref, toRef } from 'vue'
pinia.use(({ store }) => {
const hello = ref('word')
store.$state.hello = hello
store.hello = toRef(store.$state, 'hello')
})
也能够在 use 办法外面界说一个状况,共享全局的ref或computed :
import { ref } from 'vue'
const globalSecret = ref('secret')
pinia.use(({ store }) => {
// `secret` 在一切 store 之间共享
store.$state.secret = globalSecret
store.secret = globalSecret
})
界说 Store 时增加新选项
能够在界说 store 时增加新的选项,以便在插件中运用它们。例如,能够增加一个debounce选项,答应对一切操作进行去颤动:
// src/stores/search.js
import { defineStore } from 'pinia'
export const useSearchStore = defineStore('search', {
actions: {
searchContacts() {
// ...
},
searchContent() {
// ...
}
},
debounce: {
// 操作 searchContacts 防抖 300ms
searchContacts: 300,
// 操作 searchContent 防抖 500ms
searchContent: 500
}
})
然后运用插件读取该选项,包装并替换原始操作:
// src/main.js
import { createPinia } from 'pinia'
import { debounce } from 'lodash'
const pinia = createPinia()
pinia.use(({ options, store }) => {
if (options.debounce) {
// 咱们正在用新的 action 覆盖原有的 action
return Object.keys(options.debounce).reduce((debouncedActions, action) => {
debouncedActions[action] = debounce(
store[action],
options.debounce[action]
)
return debouncedActions
}, {})
}
})
这样在组件中运用 actions 的办法就能够去颤动了,是不是很便利!
完成本地存储
信任大家运用 Vuex 都有这样的困惑,F5 改写一下数据全没了。在咱们眼里这很正常,但在测试同学眼里这便是一个 bug 。Vuex 中完成本地存储比较费事,需要把状况一个一个存储到本地,取数据时也要进行处理。而运用 Pinia ,一个插件就能够搞定。
这次咱们就不自己写了,直接装置开源插件。
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 状况,默许存储一切的状况
}
]
}
ok,今日的共享便是这些。不知道你对 Pinia 是不是有了更进一步的了解,欢迎评论区留言评论。
小结
Pinia 整体来说比 Vuex 愈加简略、轻量,但功用却愈加强壮,也许这便是它取代 Vuex 的原因吧。此外,Pinia 还能够在 Vue2 中结合 map 函数运用,有爱好的同学能够研究一下。
欢迎重视专栏 Vue3 特训营 ,后边我会持续共享更多 Vue3 相关的内容。如果对你有所协助,记住点个赞呦!
参阅文档:Pinia 中文文档