一文帶你上手Vue新的狀態(tài)管理Pinia
Vuex 作為一個(gè)老牌 Vue 狀態(tài)管理庫(kù),大家都很熟悉了
Pinia 是 Vue.js 團(tuán)隊(duì)成員專門為 Vue 開(kāi)發(fā)的一個(gè)全新的狀態(tài)管理庫(kù),并且已經(jīng)被納入官方 github
為什么有 Vuex 了還要再開(kāi)發(fā)一個(gè) Pinia ?
先來(lái)一張圖,看下當(dāng)時(shí)對(duì)于 Vuex5 的提案,就是下一代 Vuex5 應(yīng)該是什么樣子的

Pinia 就是完整的符合了他當(dāng)時(shí) Vuex5 提案所提到的功能點(diǎn),所以可以說(shuō) Pinia 就是 Vuex5 也不為過(guò),因?yàn)樗淖髡呔褪枪俜降拈_(kāi)發(fā)人員,并且已經(jīng)被官方接管了,只是目前 Vuex 和 Pinia 還是兩個(gè)獨(dú)立的倉(cāng)庫(kù),以后可能會(huì)合并,也可能獨(dú)立發(fā)展,只是官方肯定推薦的是 Pinia
因?yàn)樵?Vue3 中使用 Vuex 的話需要使用 Vuex4,還只能作為一個(gè)過(guò)渡的選擇,存在很大缺陷,所以在 Componsition API 誕生之后,也就設(shè)計(jì)了全新的狀態(tài)管理 Pinia
Pinia 和 Vuex
Vuex: State、Gettes、Mutations(同步)、Actions(異步)
Pinia: State、Gettes、Actions(同步異步都支持)
Vuex 當(dāng)前最新版是 4.x
- Vuex4 用于 Vue3
- Vuex3 用于 Vue2
Pinia 當(dāng)前最新版是 2.x
- 即支持 Vue2 也支持 Vue3
就目前而言 Pinia 比 Vuex 好太多了,解決了 Vuex 的很多問(wèn)題,所以筆者也非常建議直接使用 Pinia,尤其是 TypeScript 的項(xiàng)目
Pinia 核心特性
1.Pinia 沒(méi)有 Mutations
2.Actions支持同步和異步
3.沒(méi)有模塊的嵌套結(jié)構(gòu)
Pinia 通過(guò)設(shè)計(jì)提供扁平結(jié)構(gòu),就是說(shuō)每個(gè) store 都是互相獨(dú)立的,誰(shuí)也不屬于誰(shuí),也就是扁平化了,更好的代碼分割且沒(méi)有命名空間。當(dāng)然你也可以通過(guò)在一個(gè)模塊中導(dǎo)入另一個(gè)模塊來(lái)隱式嵌套 store,甚至可以擁有 store 的循環(huán)依賴關(guān)系
4.更好的 TypeScript 支持
不需要再創(chuàng)建自定義的復(fù)雜包裝器來(lái)支持 TypeScript 所有內(nèi)容都類型化,并且 API 的設(shè)計(jì)方式也盡可能的使用 TS 類型推斷
5.不需要注入、導(dǎo)入函數(shù)、調(diào)用它們,享受自動(dòng)補(bǔ)全,讓我們開(kāi)發(fā)更加方便
6.無(wú)需手動(dòng)添加 store,它的模塊默認(rèn)情況下創(chuàng)建就自動(dòng)注冊(cè)的
7.Vue2 和 Vue3 都支持
除了初始化安裝和SSR配置之外,兩者使用上的API都是相同的
8.支持 Vue DevTools
- 跟蹤 actions, mutations 的時(shí)間線
- 在使用了模塊的組件中就可以觀察到模塊本身
- 支持 time-travel 更容易調(diào)試
- 在 Vue2 中 Pinia 會(huì)使用 Vuex 的所有接口,所以它倆不能一起使用
- 但是針對(duì) Vue3 的調(diào)試工具支持還不夠完美,比如還沒(méi)有 time-travel 功能
9.模塊熱更新
- 無(wú)需重新加載頁(yè)面就可以修改模塊
- 熱更新的時(shí)候會(huì)保持任何現(xiàn)有狀態(tài)
10.支持使用插件擴(kuò)展 Pinia 功能
11.支持服務(wù)端渲染
Pinia 使用
以 Vue3 + TypeScript 為例
安裝
npm install pinia
main.ts 初始化配置
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')
在 store 目錄下創(chuàng)建一個(gè) user.ts 為例,我們先定義并導(dǎo)出一個(gè)名為 user 的模塊
import { defineStore } from 'pinia'
export const userStore = defineStore('user', {
state: () => {
return {
count: 1,
arr: []
}
},
getters: { ... },
actions: { ... }
})defineStore 接收兩個(gè)參數(shù)
第一個(gè)參數(shù)就是模塊的名稱,必須是唯一的,多個(gè)模塊不能重名,Pinia 會(huì)把所有的模塊都掛載到根容器上
第二個(gè)參數(shù)是一個(gè)對(duì)象,里面的選項(xiàng)和 Vuex 差不多
- 其中
state用來(lái)存儲(chǔ)全局狀態(tài),它必須是箭頭函數(shù),為了在服務(wù)端渲染的時(shí)候避免交叉請(qǐng)求導(dǎo)致的數(shù)據(jù)狀態(tài)污染所以只能是函數(shù),而必須用箭頭函數(shù)則為了更好的 TS 類型推導(dǎo) getters就是用來(lái)封裝計(jì)算屬性,它有緩存的功能actions就是用來(lái)封裝業(yè)務(wù)邏輯,修改 state
訪問(wèn) state
比如我們要在頁(yè)面中訪問(wèn) state 里的屬性 count
由于 defineStore 會(huì)返回一個(gè)函數(shù),所以要先調(diào)用拿到數(shù)據(jù)對(duì)象,然后就可以在模板中直接使用了
<template>
<div>{{ user_store.count }}</div>
</template>
<script lang="ts" setup>
import { userStore } from '../store'
const user_store = userStore()
// 解構(gòu)
// const { count } = userStore()
</script>比如像注釋中的解構(gòu)出來(lái)使用,是完全沒(méi)有問(wèn)題的,只是注意了,這樣拿到的數(shù)據(jù)不是響應(yīng)式的,如果要解構(gòu)還保持響應(yīng)式就要用到一個(gè)方法 storeToRefs(),示例如下
<template>
<div>{{ count }}</div>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { userStore } from '../store'
const { count } = storeToRefs(userStore())
</script>原因就是 Pinia 其實(shí)是把 state 數(shù)據(jù)都做了 reactive 處理,和 Vue3 的 reactive 同理,解構(gòu)出來(lái)的也不是響應(yīng)式,所以需要再做 ref 響應(yīng)式代理
getters
這個(gè)和 Vuex 的 getters 一樣,也有緩存功能。如下在頁(yè)面中多次使用,第一次會(huì)調(diào)用 getters,數(shù)據(jù)沒(méi)有改變的情況下之后會(huì)讀取緩存
<template>
<div>{{ myCount }}</div>
<div>{{ myCount }}</div>
<div>{{ myCount }}</div>
</template>注意兩種方法的區(qū)別,寫在注釋里了
getters: {
// 方法一,接收一個(gè)可選參數(shù) state
myCount(state){
console.log('調(diào)用了') // 頁(yè)面中使用了三次,這里只會(huì)執(zhí)行一次,然后緩存起來(lái)了
return state.count + 1
},
// 方法二,不傳參數(shù),使用 this
// 但是必須指定函數(shù)返回值的類型,否則類型推導(dǎo)不出來(lái)
myCount(): number{
return this.count + 1
}
}更新和 actions
更新 state 里的數(shù)據(jù)有四種方法,我們先看三種簡(jiǎn)單的更新,說(shuō)明都寫在注釋里了
<template>
<div>{{ user_store.count }}</div>
<button @click="handleClick">按鈕</button>
</template>
<script lang="ts" setup>
import { userStore } from '../store'
const user_store = userStore()
const handleClick = () => {
// 方法一
user_store.count++
// 方法二,需要修改多個(gè)數(shù)據(jù),建議用 $patch 批量更新,傳入一個(gè)對(duì)象
user_store.$patch({
count: user_store.count1++,
// arr: user_store.arr.push(1) // 錯(cuò)誤
arr: [ ...user_store.arr, 1 ] // 可以,但是還得把整個(gè)數(shù)組都拿出來(lái)解構(gòu),就沒(méi)必要
})
// 使用 $patch 性能更優(yōu),因?yàn)槎鄠€(gè)數(shù)據(jù)更新只會(huì)更新一次視圖
// 方法三,還是$patch,傳入函數(shù),第一個(gè)參數(shù)就是 state
user_store.$patch( state => {
state.count++
state.arr.push(1)
})
}
</script>第四種方法就是當(dāng)邏輯比較多或者請(qǐng)求的時(shí)候,我們就可以封裝到示例中 store/user.ts 里的 actions 里
可以傳參數(shù),也可以通過(guò) this.xx 可以直接獲取到 state 里的數(shù)據(jù),需要注意的是不能用箭頭函數(shù)定義 actions,不然就會(huì)綁定外部的 this 了
actions: {
changeState(num: number){ // 不能用箭頭函數(shù)
this.count += num
}
}調(diào)用
const handleClick = () => {
user_store.changeState(1)
}支持 VueDevtools
打開(kāi)開(kāi)發(fā)者工具的 Vue Devtools 就會(huì)發(fā)現(xiàn) Pinia,而且可以手動(dòng)修改數(shù)據(jù)調(diào)試,非常方便

模擬調(diào)用接口
示例:
我們先定義示例接口 api/user.ts
// 接口數(shù)據(jù)類型
export interface userListType{
id: number
name: string
age: number
}
// 模擬請(qǐng)求接口返回的數(shù)據(jù)
const userList = [
{ id: 1, name: '張三', age: 18 },
{ id: 2, name: '李四', age: 19 },
]
// 封裝模擬異步效果的定時(shí)器
async function wait(delay: number){
return new Promise((resolve) => setTimeout(resolve, delay))
}
// 接口
export const getUserList = async () => {
await wait(100) // 延遲100毫秒返回
return userList
}然后在 store/user.ts 里的 actions 封裝調(diào)用接口
import { defineStore } from 'pinia'
import { getUserList, userListType } from '../api/user'
export const userStore = defineStore('user', {
state: () => {
return {
// 用戶列表
list: [] as userListType // 類型轉(zhuǎn)換成 userListType
}
},
actions: {
async loadUserList(){
const list = await getUserList()
this.list = list
}
}
})頁(yè)面中調(diào)用 actions 發(fā)起請(qǐng)求
<template>
<ul>
<li v-for="item in user_store.list"> ... </li>
</ul>
</template>
<script lang="ts" setup>
import { userStore } from '../store'
const user_store = userStore()
user_store.loadUserList() // 加載所有數(shù)據(jù)
</script>跨模塊修改數(shù)據(jù)
在一個(gè)模塊的 actions 里需要修改另一個(gè)模塊的 state 數(shù)據(jù)
示例:比如在 chat 模塊里修改 user 模塊里某個(gè)用戶的名稱
// chat.ts
import { defineStore } from 'pinia'
import { userStore } from './user'
export const chatStore = defineStore('chat', {
actions: {
someMethod(userItem){
userItem.name = '新的名字'
const user_store = userStore()
user_store.updateUserName(userItem)
}
}
})user 模塊里
// user.ts
import { defineStore } from 'pinia'
export const userStore = defineStore('user', {
state: () => {
return {
list: []
}
},
actions: {
updateUserName(userItem){
const user = this.list.find(item => item.id === userItem.id)
if(user){
user.name = userItem.name
}
}
}
})到此這篇關(guān)于一文帶你上手Vue新的狀態(tài)管理Pinia的文章就介紹到這了,更多相關(guān)Vue狀態(tài)管理Pinia內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue 使用Jade模板寫html,stylus寫css的方法
這篇文章主要介紹了vue 使用Jade模板寫html,stylus寫css的方法,文中還給大家提到了使用jade注意事項(xiàng),需要的朋友可以參考下2018-02-02
Vue computed實(shí)現(xiàn)原理深入講解
computed又被稱作計(jì)算屬性,用于動(dòng)態(tài)的根據(jù)某個(gè)值或某些值的變化,來(lái)產(chǎn)生對(duì)應(yīng)的變化,computed具有緩存性,當(dāng)無(wú)關(guān)值變化時(shí),不會(huì)引起computed聲明值的變化。產(chǎn)生一個(gè)新的變量并掛載到vue實(shí)例上去2022-10-10
詳解如何將 Vue-cli 改造成支持多頁(yè)面的 history 模式
本篇文章主要介紹了詳解如何將 Vue-cli 改造成支持多頁(yè)面的 history 模式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
Vue 監(jiān)聽(tīng)元素前后變化值實(shí)例
這篇文章主要介紹了Vue 監(jiān)聽(tīng)元素前后變化值實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07
前端Vue設(shè)置cookie、刪除cookie,獲取cookie方式
這篇文章主要介紹了前端Vue設(shè)置cookie、刪除cookie,獲取cookie方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
Vue自定義render統(tǒng)一項(xiàng)目組彈框功能
這篇文章主要介紹了Vue自定義render統(tǒng)一項(xiàng)目組彈框功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
vue生命周期beforeDestroy和destroyed調(diào)用方式
這篇文章主要介紹了vue生命周期beforeDestroy和destroyed調(diào)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06

