上手 Vue 新的狀態(tài)管理 Pinia新蟆,一篇文章就夠了

Vuex 作為一個老牌 Vue 狀態(tài)管理庫训柴,大家都很熟悉了

Pinia 是 Vue.js 團隊成員專門為 Vue 開發(fā)的一個全新的狀態(tài)管理庫缸濒,并且已經(jīng)被納入官方 github

為什么有 Vuex 了還要再開發(fā)一個 Pinia ?

先來一張圖植榕,看下當時對于 Vuex5 的提案再沧,就是下一代 Vuex5 應該是什么樣子的

微信圖片_20220314212501.png

Pinia 就是完整的符合了他當時 Vuex5 提案所提到的功能點,所以可以說 Pinia 就是 Vuex5 也不為過尊残,因為它的作者就是官方的開發(fā)人員炒瘸,并且已經(jīng)被官方接管了,只是目前 Vuex 和 Pinia 還是兩個獨立的倉庫寝衫,以后可能會合并顷扩,也可能獨立發(fā)展,只是官方肯定推薦的是 Pinia

因為在 Vue3 中使用 Vuex 的話需要使用 Vuex4竞端,并且還只能作為一個過渡的選擇屎即,存在很大缺陷,所以在 Componsition API 誕生之后事富,也就設計了全新的狀態(tài)管理 Pinia

Pinia 和 Vuex

VuexState技俐、GettesMutations(同步)统台、Actions(異步)

PiniaState雕擂、GettesActions(同步異步都支持)

Vuex 當前最新版是 4.x

  • Vuex4 用于 Vue3
  • Vuex3 用于 Vue2

Pinia 當前最新版是 2.x

  • 即支持 Vue2 也支持 Vue3

就目前而言 Pinia 比 Vuex 好太多了贱勃,解決了 Vuex 的很多問題井赌,所以筆者也非常建議直接使用 Pinia,尤其是 TypeScript 的項目

Pinia 核心特性

  • Pinia 沒有 Mutations
  • Actions 支持同步和異步
  • 沒有模塊的嵌套結(jié)構(gòu)
    • Pinia 通過設計提供扁平結(jié)構(gòu)贵扰,就是說每個 store 都是互相獨立的仇穗,誰也不屬于誰,也就是扁平化了戚绕,更好的代碼分割且沒有命名空間纹坐。當然你也可以通過在一個模塊中導入另一個模塊來隱式嵌套 store,甚至可以擁有 store 的循環(huán)依賴關(guān)系
  • 更好的 TypeScript 支持
    • 不需要再創(chuàng)建自定義的復雜包裝器來支持 TypeScript 所有內(nèi)容都類型化舞丛,并且 API 的設計方式也盡可能的使用 TS 類型推斷
  • 不需要注入耘子、導入函數(shù)果漾、調(diào)用它們,享受自動補全谷誓,讓我們開發(fā)更加方便
  • 無需手動添加 store绒障,它的模塊默認情況下創(chuàng)建就自動注冊的
  • Vue2 和 Vue3 都支持
    • 除了初始化安裝和SSR配置之外,兩者使用上的API都是相同的
  • 支持 Vue DevTools
    • 跟蹤 actions, mutations 的時間線
    • 在使用了模塊的組件中就可以觀察到模塊本身
    • 支持 time-travel 更容易調(diào)試
    • 在 Vue2 中 Pinia 會使用 Vuex 的所有接口捍歪,所以它倆不能一起使用
    • 但是針對 Vue3 的調(diào)試工具支持還不夠完美户辱,比如還沒有 time-travel 功能
  • 模塊熱更新
    • 無需重新加載頁面就可以修改模塊
    • 熱更新的時候會保持任何現(xiàn)有狀態(tài)
  • 支持使用插件擴展 Pinia 功能
  • 支持服務端渲染

Pinia 使用

Vue3 + TypeScript 為例

安裝

npm install pinia

main.ts 初始化配置

import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')

在 store 目錄下創(chuàng)建一個 user.ts 為例,我們先定義并導出一個名為 user 的模塊

import { defineStore } from 'pinia'
export const userStore = defineStore('user', {
    state: () => {
        return { 
            count: 1,
            arr: []
        }
    },
    getters: { ... },
    actions: { ... }
})

defineStore 接收兩個參數(shù)

第一個參數(shù)就是模塊的名稱费封,必須是唯一的焕妙,多個模塊不能重名蒋伦,Pinia 會把所有的模塊都掛載到根容器上
第二個參數(shù)是一個對象弓摘,里面的選項和 Vuex 差不多

  • 其中 state 用來存儲全局狀態(tài),它必須是箭頭函數(shù)痕届,為了在服務端渲染的時候避免交叉請求導致的數(shù)據(jù)狀態(tài)污染所以只能是函數(shù)韧献,而必須用箭頭函數(shù)則為了更好的 TS 類型推導
  • getters 就是用來封裝計算屬性,它有緩存的功能
  • actions 就是用來封裝業(yè)務邏輯研叫,修改 state

訪問 state

比如我們要在頁面中訪問 state 里的屬性 count

由于 defineStore 會返回一個函數(shù)锤窑,所以要先調(diào)用拿到數(shù)據(jù)對象,然后就可以在模板中直接使用了

<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)出來使用嚷炉,是完全沒有問題的渊啰,只是注意了,這樣拿到的數(shù)據(jù)不是響應式的申屹,如果要解構(gòu)還保持響應式就要用到一個方法 storeToRefs()绘证,示例如下

<template>
    <div>{{ count }}</div>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { userStore } from '../store'
const { count } = storeToRefs(userStore)
</script>

原因就是 Pinia 其實是把 state 數(shù)據(jù)都做了 reactive 處理晨缴,和 Vue3 的 reactive 同理寓涨,解構(gòu)出來的也不是響應式,所以需要再做 ref 響應式代理

getters

這個和 Vuex 的 getters 一樣压真,也有緩存功能杆煞。如下在頁面中多次使用魏宽,第一次會調(diào)用 getters,數(shù)據(jù)沒有改變的情況下之后會讀取緩存

<template>
    <div>{{ myCount }}</div>
    <div>{{ myCount }}</div>
    <div>{{ myCount }}</div>
</template>

注意兩種方法的區(qū)別决乎,寫在注釋里了

getters: {
    // 方法一队询,接收一個可選參數(shù) state
    myCount(state){
        console.log('調(diào)用了') // 頁面中使用了三次,這里只會執(zhí)行一次构诚,然后緩存起來了
        return state.count + 1
    },
    // 方法二蚌斩,不傳參數(shù),使用 this
    // 但是必須指定函數(shù)返回值的類型唤反,否則類型推導不出來
    myCount(): number{
        return this.count + 1
    }
}

更新和 actions

更新 state 里的數(shù)據(jù)有四種方法凳寺,我們先看三種簡單的更新鸭津,說明都寫在注釋里了

<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++
    
    // 方法二,需要修改多個數(shù)據(jù)肠缨,建議用 $patch 批量更新逆趋,傳入一個對象
    user_store.$patch({
        count: user_store.count1++,
        // arr: user_store.arr.push(1) // 錯誤
        arr: [ ...user_store.arr, 1 ] // 可以,但是還得把整個數(shù)組都拿出來解構(gòu)晒奕,就沒必要
    })
    
    // 使用 $patch 性能更優(yōu)闻书,因為多個數(shù)據(jù)更新只會更新一次視圖
    
    // 方法三,還是$patch脑慧,傳入函數(shù)魄眉,第一個參數(shù)就是 state
    user_store.$patch( state => {
        state.count++
        state.arr.push(1)
    })
}
</script>

第四種方法就是當邏輯比較多或者請求的時候,我們就可以封裝到示例中 store/user.ts 里的 actions 里

可以傳參數(shù)闷袒,也可以通過 this.xx 可以直接獲取到 state 里的數(shù)據(jù)坑律,需要注意的是不能用箭頭函數(shù)定義 actions,不然就會綁定外部的 this 了

actions: {
    changeState(num: number){ // 不能用箭頭函數(shù)
        this.count += num
    }
}

調(diào)用

const handleClick = () => {
    user_store.changeState(1)
}

支持 VueDevtools

打開開發(fā)者工具的 Vue Devtools 就會發(fā)現(xiàn) Pinia囊骤,而且可以手動修改數(shù)據(jù)調(diào)試晃择,非常方便

ark.png

模擬調(diào)用接口

示例:

我們先定義示例接口 api/user.ts

// 接口數(shù)據(jù)類型
export interface userListType{
    id: number
    name: string
    age: number
}
// 模擬請求接口返回的數(shù)據(jù)
const userList = [
    { id: 1, name: '張三', age: 18 },
    { id: 2, name: '李四', age: 19 },
]
// 封裝模擬異步效果的定時器
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
        }
    }
})

頁面中調(diào)用 actions 發(fā)起請求

<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ù)

在一個模塊的 actions 里需要修改另一個模塊的 state 數(shù)據(jù)

示例:比如在 chat 模塊里修改 user 模塊里某個用戶的名稱

// 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
            }
        }
    }
})

結(jié)語

如果本文對你有一點點幫助,點個贊支持一下吧也物,你的每一個【贊】都是我創(chuàng)作的最大動力宫屠,感謝支持 _

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市滑蚯,隨后出現(xiàn)的幾起案子浪蹂,更是在濱河造成了極大的恐慌,老刑警劉巖告材,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坤次,死亡現(xiàn)場離奇詭異,居然都是意外死亡创葡,警方通過查閱死者的電腦和手機浙踢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灿渴,“玉大人洛波,你說我怎么就攤上這事∩叮” “怎么了蹬挤?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長棘幸。 經(jīng)常有香客問我焰扳,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任吨悍,我火速辦了婚禮扫茅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘育瓜。我一直安慰自己葫隙,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布躏仇。 她就那樣靜靜地躺著恋脚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪焰手。 梳的紋絲不亂的頭發(fā)上糟描,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音书妻,去河邊找鬼船响。 笑死,一個胖子當著我的面吹牛驻子,可吹牛的內(nèi)容都是我干的灿意。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼崇呵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了馅袁?” 一聲冷哼從身側(cè)響起域慷,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎汗销,沒想到半個月后犹褒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡弛针,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年叠骑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片削茁。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡宙枷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茧跋,到底是詐尸還是另有隱情慰丛,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布瘾杭,位于F島的核電站诅病,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贤笆,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一蝇棉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芥永,春花似錦银萍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至飞袋,卻和暖如春戳气,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背巧鸭。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工瓶您, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纲仍。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓呀袱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親郑叠。 傳聞我的和親對象是個殘疾皇子夜赵,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容