尝试过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,那么我们需要指定返回值类型。
- 如果接受了,可以不指定返回值类型。