關(guān)于Pinia狀態(tài)管理解讀
1、Pinia和Vuex的對比
1.1、什么是Pinia呢?
Pinia(發(fā)音為/pi?nj?/,如英語中的“peenya”)是最接近piña(西班牙語中的菠蘿)的詞;
- Pinia開始于大概2019年,最初是作為一個實驗為Vue重新設(shè)計狀態(tài)管理,讓它用起來像組合式API(Composition API)。
- 從那時到現(xiàn)在,最初的設(shè)計原則依然是相同的,并且目前同時兼容Vue2、Vue3,也并不要求你使用Composition API;
- Pinia本質(zhì)上依然是一個狀態(tài)管理的庫,用于跨組件、頁面進行狀態(tài)共享(這點和Vuex、Redux一樣);
1.2、Pinia和Vuex的區(qū)別
那么我們不是已經(jīng)有Vuex了嗎?為什么還要用Pinia呢?
- Pinia 最初是為了探索 Vuex 的下一次迭代會是什么樣子,結(jié)合了 Vuex 5 核心團隊討論中的許多想法;
- 最終,團隊意識到Pinia已經(jīng)實現(xiàn)了Vuex5中大部分內(nèi)容,所以最終決定用Pinia來替代Vuex;
- 與 Vuex 相比,Pinia 提供了一個更簡單的 API,具有更少的儀式,提供了 Composition-API 風格的 API;
- 最重要的是,在與 TypeScript 一起使用時具有可靠的類型推斷支持;
和Vuex相比,Pinia有很多的優(yōu)勢:
比如mutations 不再存在:
- 他們經(jīng)常被認為是非常冗長;
- 他們最初帶來了 devtools 集成,但這不再是問題;
更友好的TypeScript支持,Vuex之前對TS的支持很不友好;
不再有modules的嵌套結(jié)構(gòu):
- 你可以靈活使用每一個store,它們是通過扁平化的方式來相互使用的;
也不再有命名空間的概念,不需要記住它們的復雜關(guān)系;
1.3、如何使用Pinia?
使用Pinia之前,我們需要先對其進行安裝:
yarn add pinia # 或者使用 npm npm install pinia
創(chuàng)建一個pinia并且將其傳遞給應(yīng)用程序:
2、創(chuàng)建Pinia的Store
2.1、認識Store
什么是Store?
- 一個 Store (如 Pinia)是一個實體,它會持有為綁定到你組件樹的狀態(tài)和業(yè)務(wù)邏輯,也就是保存了全局的狀態(tài);
- 它有點像始終存在,并且每個人都可以讀取和寫入的組件;
- 你可以在你的應(yīng)用程序中定義任意數(shù)量的Store來管理你的狀態(tài);
Store有三個核心概念:
- state、getters、actions;
- 等同于組件的data、computed、methods;
- 一旦 store 被實例化,你就可以直接在 store 上訪問 state、getters 和 actions 中定義的任何屬性;
2.2、定義一個Store
定義一個Store:
我們需要知道 Store 是使用 defineStore() 定義的,
并且它需要一個唯一名稱,作為第一個參數(shù)傳遞;
這個 name,也稱為 id,是必要的,Pinia 使用它來將 store 連接到 devtools。
返回的函數(shù)統(tǒng)一使用useX作為命名方案,這是約定的規(guī)范;
2.3、使用定義的Store
Store在它被使用之前是不會創(chuàng)建的,我們可以通過調(diào)用use函數(shù)來使用Store:
注意:Store獲取到后不能被解構(gòu),那么會失去響應(yīng)式:
為了從 Store 中提取屬性同時保持其響應(yīng)式,您需要使用storeToRefs()。
2.4、示例代碼
index.js
import {createPinia} from 'pinia' // 創(chuàng)建pinia const pinia = createPinia() export default pinia
counter.js
// 定義關(guān)于counter的store import {defineStore} from 'pinia' // 參數(shù)一為標識名 // 返回值為一個函數(shù) const useCounter = defineStore("counter", { state: () => ({ count: 99 }) }) export default useCounter
Home.vue
<template> <div class="home"> <h2>Home View</h2> <h2>count: {{ counterStore.count }}</h2> <h2>count: {{ count }}</h2> <button @click="incrementCount">count+1</button> </div> </template> <script setup> import {toRefs} from 'vue' import {storeToRefs} from 'pinia' import useCounter from '@/stores/counter'; // 調(diào)用函數(shù),拿到store對象 const counterStore = useCounter() // 解構(gòu)對象(解構(gòu)出來的對象會失去響應(yīng)式) // const { count } = toRefs(counterStore) // storeToRefs這是vue提供的,作用與toRefs相同 const {count} = storeToRefs(counterStore) // 修改數(shù)據(jù) function incrementCount() { counterStore.count++ } </script> <style scoped> </style>
App.vue
<template> <div class="app"> <h2>App Component</h2> <hr> <home/> </div> </template> <script setup> import Home from './views/Home.vue' </script> <style> </style>
main.js
import {createApp} from 'vue' import App from './App.vue' import pinia from './stores/index.js' createApp(App).use(pinia).mount('#app')
注意:index.js、App.vue、main.js接下來都不會發(fā)生改變了,所以下面的示例代碼就不會寫出來了。
3、Pinia核心概念State
3.1、認識和定義State
state 是 store 的核心部分,因為store是用來幫助我們管理狀態(tài)的。
在 Pinia 中,狀態(tài)被定義為返回初始狀態(tài)的函數(shù);
3.2、操作State(一)
讀取和寫入 state:
默認情況下,您可以通過 store 實例訪問狀態(tài)來直接讀取和寫入狀態(tài);
重置 State:
你可以通過調(diào)用 store 上的 $reset() 方法將狀態(tài) 重置 到其初始值;
3.3、操作State(二)
改變State:
除了直接用 store.counter++ 修改 store,你還可以調(diào)用 $patch 方法;
它允許您使用部分“state”對象同時應(yīng)用多個更改;
替換State:
您可以通過將其 $state 屬性設(shè)置為新對象來替換 Store 的整個狀態(tài):
3.4、代碼示例
Home.vue
<template> <div class="home"> <h2>Home View</h2> <h2>name: {{ name }}</h2> <h2>age: {{ age }}</h2> <h2>level: {{ level }}</h2> <button @click="changeState">修改state</button> <button @click="resetState">重置state</button> </div> </template> <script setup> import useUser from '@/stores/user' import {storeToRefs} from 'pinia'; const userStore = useUser() const {name, age, level} = storeToRefs(userStore) function changeState() { // 1.一個個修改狀態(tài) // userStore.name = "kobe" // userStore.age = 20 // userStore.level = 200 // 2.一次性修改多個狀態(tài) // userStore.$patch({ // name: "james", // age: 35 // }) // 3.替換state為新的對象 const oldState = userStore.$state userStore.$state = { name: "curry", level: 200 } console.log(oldState === userStore.$state) } function resetState() { userStore.$reset() // 重置state } </script> <style scoped> </style>
user.js
import {defineStore} from 'pinia' const useUser = defineStore("user", { state: () => ({ name: "why", age: 18, level: 100 }) }) export default useUser
4、Pinia核心概念Getters
4.1、認識和定義Getters
Getters相當于Store的計算屬性:
它們可以用 defineStore() 中的 getters 屬性定義;
getters中可以定義接受一個state作為參數(shù)的函數(shù);
4.2、訪問Getters(一)
訪問當前store的Getters:
Getters中訪問自己的其他Getters:
我們可以通過this來訪問到當前store實例的所有其他屬性;
訪問其他store的Getters:
4.3、訪問Getters(二)
Getters也可以返回一個函數(shù),這樣就可以接受參數(shù):
4.4、代碼示例
counter.js
// 定義關(guān)于counter的store import {defineStore} from 'pinia' import useUser from './user.js' const useCounter = defineStore("counter", { state: () => ({ count: 99, friends: [ {id: 111, name: "why"}, {id: 112, name: "kobe"}, {id: 113, name: "james"}, ] }), getters: { // 1.基本使用 doubleCount(state) { return state.count * 2 }, // 2.一個getter引入另外一個getter doubleCountAddOne() { // this是store實例 return this.doubleCount + 1 }, // 3.getters也支持返回一個函數(shù) getFriendById(state) { return function (id) { for (let i = 0; i < state.friends.length; i++) { const friend = state.friends[i] if (friend.id === id) { return friend } } } }, // 4.getters中用到別的store中的數(shù)據(jù) showMessage(state) { // 1.獲取user信息 const userStore = useUser() // 2.獲取自己的信息 // 3.拼接信息 return `name:${userStore.name}-count:${state.count}` } } }) export default useCounter
Home.vue
<template> <div class="home"> <h2>Home View</h2> <h2>doubleCount: {{ counterStore.doubleCount }}</h2> <h2>doubleCountAddOne: {{ counterStore.doubleCountAddOne }}</h2> <h2>friend-111: {{ counterStore.getFriendById(111) }}</h2> <h2>friend-112: {{ counterStore.getFriendById(112) }}</h2> <h2>showMessage: {{ counterStore.showMessage }}</h2> <button @click="changeState">修改state</button> <button @click="resetState">重置state</button> </div> </template> <script setup> import useCounter from '@/stores/counter'; const counterStore = useCounter() </script> <style scoped> </style>
5、Pinia核心概念Actions
5.1、認識和定義Actions
Actions 相當于組件中的 methods。
可以使用 defineStore() 中的 actions 屬性定義,并且它們非常適合定義業(yè)務(wù)邏輯;
和getters一樣,在action中可以通過this訪問整個store實例的所有操作;
5.2、Actions執(zhí)行異步操作
并且Actions中是支持異步操作的,并且我們可以編寫異步函數(shù),在函數(shù)中使用await;
5.3、代碼示例
counter.js
// 定義關(guān)于counter的store import {defineStore} from 'pinia' import useUser from './user' const useCounter = defineStore("counter", { state: () => ({ count: 99, friends: [ {id: 111, name: "why"}, {id: 112, name: "kobe"}, {id: 113, name: "james"}, ] }), getters: { // 1.基本使用 doubleCount(state) { return state.count * 2 }, // 2.一個getter引入另外一個getter doubleCountAddOne() { // this是store實例 return this.doubleCount + 1 }, // 3.getters也支持返回一個函數(shù) getFriendById(state) { return function (id) { for (let i = 0; i < state.friends.length; i++) { const friend = state.friends[i] if (friend.id === id) { return friend } } } }, // 4.getters中用到別的store中的數(shù)據(jù) showMessage(state) { // 1.獲取user信息 const userStore = useUser() // 2.獲取自己的信息 // 3.拼接信息 return `name:${userStore.name}-count:${state.count}` } }, actions: { increment() { this.count++ }, incrementNum(num) { this.count += num } } }) export default useCounter
home.js
import {defineStore} from 'pinia' const useHome = defineStore("home", { state: () => ({ banners: [], recommends: [] }), actions: { async fetchHomeMultidata() { // fetchHomeMultidata() { const res = await fetch("http://123.207.32.32:8000/home/multidata") const data = await res.json() this.banners = data.data.banner.list this.recommends = data.data.recommend.list // return new Promise(async (resolve, reject) => { // const res = await fetch("http://123.207.32.32:8000/home/multidata") // const data = await res.json() // this.banners = data.data.banner.list // this.recommends = data.data.recommend.list // resolve("bbb") // }) } } }) export default useHome
Home.vue
<template> <div class="home"> <h2>Home View</h2> <h2>doubleCount: {{ counterStore.count }}</h2> <button @click="changeState">修改state</button> <!-- 展示數(shù)據(jù) --> <h2>輪播的數(shù)據(jù)</h2> <ul> <template v-for="item in homeStore.banners"> <li>{{ item.title }}</li> </template> </ul> </div> </template> <script setup> import useCounter from '@/stores/counter'; import useHome from '@/stores/home'; const counterStore = useCounter() function changeState() { // counterStore.increment() counterStore.incrementNum(10) } const homeStore = useHome() homeStore.fetchHomeMultidata().then(res => { console.log("fetchHomeMultidata的action已經(jīng)完成了:", res) }) </script> <style scoped> </style>
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue如何實現(xiàn)數(shù)據(jù)的上移和下移
這篇文章主要介紹了Vue如何實現(xiàn)數(shù)據(jù)的上移和下移問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06解讀計算屬性和watch監(jiān)聽的區(qū)別及說明
計算屬性是基于它們的依賴進行緩存的,只有在它的相關(guān)依賴發(fā)生改變時才會重新求值,而watch則是一個更為通用的監(jiān)聽器,它可以在數(shù)據(jù)變化時執(zhí)行異步操作或開銷較大的操作2025-01-01