尝试过vuex @4.x的同学都知道,对ts的支持不是很好,写起来不是很方便。不了解的同学请看这篇文章。
pinia简单介绍
Pinia是新一代的状态管理器,由 Vue.js团队中成员Phan An所开发的,因此也被认为是下一代的 Vuex,即 Vuex5.x。并被加入官方账户下。
Pinia 有如下特点:
- 支持options api和composition api。
- 完整的 typescript 的支持。
- 去除 mutations,只有 state,getters,actions。
- actions 支持同步和异步。
- 没有模块嵌套,扁平式的模块组织方式,极大的简化了代码书写过程。
- 自动代码分割。
- 支持vue devtools。
安装
npm install pinia
// yarn add pinia
在vue中注册该库。
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
核心概念
pinia从使用角度和vuex几乎一样。通过defineStore
来创建一个容器对象。
state
就是以前vuex中的state。类似于组件的data,保存该模块下的数据。
import { defineStore } from 'pinia'
const useUserStore = defineStore('user', {
state: () => {
return {
name "zh",
age: 20,
friends: []
}
}
})
如何获取和修改该state中的数据呢?下面就来介绍一下。
获取state属性
直接导入该模块
<ul>
<li>姓名: {{ userStore.name }}</li>
<li>年龄: {{ userStore.age }}</li>
<li>朋友: {{ userStore.friends }}</li>
</ul>
import useUserStore from '../store/userStore'
const userStore = useUserStore()
看到上面取值这么麻烦,那我来解构看看吧。
<ul>
<li>姓名: {{ name }}</li>
<li>年龄: {{ age }}</li>
<li>朋友: {{ friends }}</li>
</ul>
// 不能直接解构,结构后数据将不再是响应式。
const { name, age, friends } = userStore
貌似上面也能展示,没啥问题,那么我们来尝试改变一下数据,看看界面的变化。
<h1>响应式state数据</h1>
<ul>
<li>姓名: {{ userStore.name }}</li>
<li>年龄: {{ userStore.age }}</li>
<li>朋友: {{ userStore.friends }}</li>
</ul>
<h1>非响应式state数据,通过解构</h1>
<ul>
<li>姓名: {{ name }}</li>
<li>年龄: {{ age }}</li>
<li>朋友: {{ friends }}</li>
</ul>
<button @click="handleUserState">修改数据:handleUserState</button>
const handleUserState = () => {
userStore.$patch((state) => {
state.name = 'llm'
state.age = 30
state.friends.push('llm', 'zh')
})
}
从上面的gif可以看出,基本数据类型是不会做到响应式的,但是引用数据类型可以。(这一问题,通过不同方式修改state,会有不同的效果。因为state返回数据的引用问题。直接修改后,返回的仍旧是原来的对象, 通过$patch和action派发返回的是一个对于原来对象的拷贝。)尽管如此,如果我们想要解构state,那么我们需要使用pinia提供的storeToRefs
来为我们服务。
<h1>响应式state数据,通过解构</h1>
<ul>
<li>姓名: {{ reactiveName }}</li>
<li>年龄: {{ reactiveAge }}</li>
<li>朋友: {{ reactiveFriends }}</li>
</ul>
// 将其变为响应式
const {
name: reactiveName,
age: reactiveAge,
friends: reactiveFriends,
} = storeToRefs(userStore)
修改state属性
- 直接修改
userStore.name = 'llm'
- 通过
$patch
进行批量修改。- 可以直接传入一个对象。导入模块获取state值进行修改。
- 也可以传入一个函数。该函数接收state作为参数。
// 这种方式只有基本数据类型不能响应式
userStore.$patch((state) => {
state.name = 'llm'
state.age = 30
state.friends.push('llm', 'zh')
})
// 这种方式任何数据都不能作为响应式
userStore.$patch({
name: 'llm',
age: 30,
friends: [...userStore.friends, 'zh', 'llm'],
})
- 通过action派发。
// useUserStore.js
import { defineStore } from 'pinia'
const useUserStore = defineStore('user', {
state: () => {
return {
name: 'zh',
age: 20,
friends: []
}
},
actions: {
changeState() {
this.name = 'llm'
this.age = 30
this.friends.push('zj', 'llm')
}
}
})
// vue
userStore.changeState()
这些方式,我依旧是推荐使用action派发,从而做到统一管理。解构state对象时,一定要使用storeToRefs
api。
action
上面已经提到了,就是对state做更新的。和vuex中action一样。但是用法有所改变。 如果管理数据,基本上都是用过action派发函数,利于管理。 下面来看看吧。
基本使用
获取state中的数据直接通过this
即可。
// useUserStore.js
import { defineStore } from 'pinia'
const useUserStore = defineStore('user', {
state: () => {
return {
name: 'zh',
age: 20,
friends: []
}
},
actions: {
changeState() {
this.name = 'llm'
this.age = 30
this.friends.push('zj', 'llm')
}
}
})
组件中使用直接通过该模块调用action函数即可。
userStore.changeState()
处理异步数据
// 更新异步数据
getSong() {
axios({
url: 'http://123.207.32.32:9001/song/detail?ids=1441758494'
}).then((res) => {
console.log('res', res.data.songs[0])
})
}
和其他store模块中的action函数使用
其实就是导入其他模块的store,然后直接调用对应的action即可。
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
preferences: null,
// ...
}),
actions: {
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
getter
用法同vuex中的getter。类似于计算属性,具有缓存功能。利于性能优化。
基本使用
- getter函数函数中可以使用其他getter。 直接通过this获取即可。
import { defineStore } from 'pinia'
const useUserStore = defineStore('user', {
// arrow function recommended for full type inference
state: () => {
return {
name: 'zh',
age: 20,
friends: [],
song: {},
discount: 0.6,
books: [
{ name: '深入Vuejs', price: 200, count: 3 },
{ name: '深入Webpack', price: 240, count: 5 },
{ name: '深入React', price: 130, count: 1 },
{ name: '深入Node', price: 220, count: 2 }
]
}
},
getters: {
currentDiscount(state) {
return state.discount * 0.9
},
totalPrice(state) {
let _totalPrice = 0
for (const book of state.books) {
_totalPrice += book.count * book.price
}
return _totalPrice * this.currentDiscount
}
}
})
- 如果想向getter函数传递参数,我们可以让getter返回一个函数,在外部调用,并传入参数即可。
getters: {
currentDiscount(state) {
return state.discount * 0.9
},
totalPrice(state) {
let _totalPrice = 0
for (const book of state.books) {
_totalPrice += book.count * book.price
}
return _totalPrice * this.currentDiscount
},
totalPriceCountGreaterN(state) {
return function (n) {
let totalPrice = 0
for (const book of state.books) {
if (book.count > n) {
totalPrice += book.count * book.price
}
}
return totalPrice * this.currentDiscount
}
}
}
// vue
<h1>getters展示</h1>
<div>{{ userStore.totalPrice }}</div>
<div>{{ userStore.totalPriceCountGreaterN(2) }}</div>
使用其他store模块的getter
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
注意事项
我们定义getter函数时,可以接受state参数。
- 如果未接受,并且使用了ts,那么我们需要指定返回值类型。
- 如果接受了,可以不指定返回值类型。
兄弟们,pinia真的很香,没有了模块的嵌套,对ts支持更是优秀,没学习的抓紧时间啊。学习过vuex的人,上手很容易。
有帮助的就点一个star吧。感谢你们。