Vue生態的新成員Pinia的詳細介紹
Pinia是Vue應用程序的狀態管理方案,是Vuex核心團隊成員開發。感覺更像是常規的舊 javascript 導入模塊,實現瞭很多Vuex5的提案。
Pinia同時支持Vue2和Vue3,不過下面展示的例子都是使用Vue3,Pinia的版本是[email protected]。
Vue2和Vue3使用的Pinia版本有一點不同,因此請查看官方 Pinia 文檔以獲取更多信息。
安裝和配置
可以使用npm或者yarn安裝Pinia
yarn add pinia@next # 或者 使用npm npm install pinia@next
安裝完畢後,找到Vue應用程序的文件 main.js (vue-cli初始化的項目,一般都命名為main.js)。從Pinia的npm包中導入createPinia,然後使用Vue的use方法作為插件使用
//main.js import { createApp } from 'vue' import App from './App.vue' import router from './router' import { createPinia } from 'pinia' createApp(App) .use(router) .use(createPinia()) .mount('#app')
接下來,我們去創建一個 store
Store核心
Pinia的Store不同於Vuex的Store。使用Vuex的時候,整個應用程序隻有一個主要Store(雖然你可以拆成不同的模塊,但是還是一個主存儲出口)。然而,今天的主角Pinia則是開箱即用的模塊化設計,不在需要一個主要的Store,可以創建不同的Store。例如,我們可以創建一個登錄用戶Store。
// store/loggedInUser.js import { defineStore } from 'pinia' export const useLoggedInUserStore = defineStore({ // id 是必填的,並且所有 Store 中唯一。因為Pinia會將它在devtools顯示 id: 'loggedInUser', state () { return { name: '太涼', age: 18, email: '[email protected]' } }, getters: {}, actions: {} })
上面我們創建瞭一個登錄用戶Store,接下來我們可以直接在組件中引用,然後在setup函數中調用useLoggedInUserStore
<template> <div>PiniaApage</div> <div>姓名:{{user.name}}</div> <div>年齡:{{user.age}}</div> </template> <script> import { useLoggedInUserStore } from '@/store/loggedInUser.js' export default { name: 'PiniaDemoPage1', setup () { const user = useLoggedInUserStore() return { user } } } </script>
這與 Vuex 有很大不同,Vuex 的Store 會自動掛載到 當前Vue的實例,可以通過this.$store的方式調用。然而,Pania 方式也讓開發人員 更清楚Store來自哪裡,因為它是標準的 Javascript 模塊導入,可以看到是從哪個文件導入的。
假如,你不用 Composition API的方式,你仍然可以借助一些輔助函數使用Pinia,下面將會詳細說,這裡先提一嘴。
State
我們將 Store 中的 state 屬性設置為一個函數,該函數返回一個包含不同狀態值的對象。這與我們在組件中定義data的方式非常相似。事實上,唯一的區別是屬性名稱:狀態與數據
import { defineStore } from 'pinia' export const useLoggedInUserStore = defineStore({ // id 是必填的,並且所有 Store 中唯一。因為Pinia會將它在devtools顯示 id: 'loggedInUser', state () { return { name: '太涼', age: 18, email: '[email protected]' } }, getters: {}, actions: {} })
現在,為瞭從組件中訪問 loginUserStore 的狀態,我們隻需要引用我們需要的Store,這種方式非常優雅。完全不需要像Vuex那樣從嵌套對象中找到我們需要的Store。
<template> <div>PiniaApage</div> <div>姓名:{{user.name}}</div> <div>年齡:{{user.age}}</div> </template> <script> import { useLoggedInUserStore } from '@/store/loggedInUser.js' export default { name: 'PiniaDemoPage1', setup () { //不用在想以前那個 user.state.name的方式獲取瞭 const user = useLoggedInUserStore() return { user } } } </script>
警告,不能結構user,因為那樣會失去響應式。下面的方式是錯誤的。
❌ const {name, email} = useLoggedInUserStore()
如果你使用的不是Composition API的方式,而是Option API的方式。可以通過Pinia的mapState函數獲取 State數據。Pinia的mapState函數 和Vuex的mapState雖然名字相同,但是使用方式完全不同。
Pinia的mapState函數的第一個參數是必須是之前創建的Store,第二個參數是Store中的state的屬性值。看代碼
//PageComponent.vue <template> <h1>你好, 我是 {{name}},我來自地球</h1> <h2>聯系郵箱:{{email}}</h2> </template> <script> import {mapState} from 'pinia' export default{ computed:{ ...mapState(useLoggedInUserStore, ['name','email']) } } </script>
總結:
- 定義Pinia的state,和組件的data的方式是一樣
- 需要在組件間中手動導入我們需要的Store模塊,這樣做的好處是明確知道數據的來源,更符合標準的 Javascript
Getters
Pinia中的getters和Vuex中的getters 的作用是相同的,都是作為組件的計算屬性(computed)。
創建getters的方式有兩種,一種是通過this關鍵字,一種是通過state具體看代碼
//store/usePostsStore.js import { defineStore } from 'pinia' export const usePostsStore = defineStore({ id: 'PostsStore', state: ()=>({ posts: ['post 1', 'post 2', 'post 3', 'post 4'] }), getters:{ // 傳統函數方式 postsCount: function(){ return this.posts.length }, // 傳統函數簡寫方式 postsCount2(){ return this.posts.length }, // 箭頭函數 postsCount3:(state)=>state.posts.length, // ❌ 不能用箭頭函數+this的方式,這種方式this指向不對 // postsCount: ()=> this.posts.length } })
接下來看Composition API方式的組件中如何使用創建的getters,其實用法和state相同。看代碼
<template> <div>PiniaBpage</div> <div> 總數:{{postsStore.postsCount}}</div> </template> <script> import { usePostsStore } from '@/store/usePostsStore.js' export default { name: 'PiniaBpage', setup () { const postsStore = usePostsStore() return { postsStore } } } </script>
如果是Option API 的組件,不能像Vuex那樣通過mapGetters輔助函數獲取。因為Pinia沒有mapGetters輔助函數,Pinia中消費getters還是借助 mapState輔助函數
<template> <div> 總數:{{postsCount}}</div> </template> <script> import { mapState } from 'pinia' import { usePostsStore } from "@/store/PostsStore"; export default { computed:{ ...mapState(usePostsStore, ['postsCount']) } }; </script>
Actions
Pinia不同於Vuex,Pinia提供瞭單一的方式更改state的值,在Pinia中沒有mutations,隻有action方式。先來看一下Pinia的action怎麼用吧。上代碼
- 直接通過this找到對應的state修改
- 通過.$patch 函數方法
- 通過.$patch 對象方法
import { defineStore } from 'pinia' export const usePostsStore = defineStore({ id: 'PostsStore', state: () => ({ posts: ['post 1', 'post 2', 'post 3', 'post 4'], user: { postsCount: 2 }, age:18, errors: [] }), getters: { postsCount: function () { return this.posts.length }, postsCount2 () { return this.posts.length }, // 箭頭函數 postsCount3: (state) => state.posts.length }, actions: { insertPost () { //方式1:直接通過this找到對應的state修改 this.posts.push(`post_${Date.now()}`) this.user.postsCount++ }, removePost () { //方式2:通過.$patch 函數方法 this.$patch((state) => { state.posts.shift() state.user.postsCount++ }) //通過.$patch 對象方法 this.$patch({ age:30 }); } } })
以上展示瞭三種更改Pinia的State方式。
如果是 Composition API使用方式
<template> <div>PiniaBpage</div> <div> 總數:{{postsStore.postsCount}}</div> <ul> <li v-for="item in postsStore.posts" :key="item" > {{item}} </li> </ul> <button @click="handleAdd">新增</button> <button @click="handleRemove">刪除</button> <button @click="handleBeforeAdd">新增到前面</button> </template> <script> import { usePostsStore } from '@/store/usePostsStore.js' export default { name: 'PiniaBpage', setup () { const postsStore = usePostsStore() // 新增 const handleAdd = () => { postsStore.insertPost() } // 刪除 const handleRemove = () => { postsStore.removePost() } // 新增到前面,也可以在這裡通過$patch修改,同樣這裡也可以直接修改 const handleBeforeAdd=()=>{ postsStore.$patch((state) => { state.posts.shift() state.user.postsCount++ }) } return { postsStore, handleAdd, handleRemove, handleBeforeAdd } } } </script>
如果是 Options API使用方式,需要借助 輔助函數 mapActions
// PostEditorComponent.vue <template> <input type="text" v-model="post" /> <button @click="insertPost(post)">保存</button> </template> <script> import {mapActions} from 'pinia' import { usePostsStore } from '@/store/PostsStore'; export default{ data(){ return { post: '' } }, methods:{ ...mapActions(usePostsStore, ['insertPost']) } } </script>
其實Pinia的action使用非常靈活
- 可以在組件或者其他actions裡面調用
- 可以在其他的Store的actions裡面調用
import { useAuthStore } from './auth-store' export const useSettingsStore = defineStore('settings', { state: () => ({ // ... }), actions: { async fetchUserPreferences(preferences) { const auth = useAuthStore() if (auth.isAuthenticated) { this.preferences = await fetchPreferences() } else { throw new Error('User must be authenticated') } }, }, })
- 支持同步和異步
- 可以支持靈活的參數
- ……………
Vue Devtools
在 Vue 2 中,Pania 支持在 Vuex 選項卡中查看狀態,甚至可以看到時間軌跡。時間軌跡的標簽幾乎沒有在 Vuex 中使用時那麼好。
至於 Vue 3,Pania 僅支持在 devtools 中查看狀態,不支持時間軌跡功能。然而,這實際上比 Vuex 為 Vue 3 提供的要多,因為它在最新的開發工具中根本不支持。
最後
快速回顧一下 Pinia 最顯著的功能,以幫助你去快速瞭解Pinia,並應用於項目中
- 由 Vue.js 核心團隊成員維護
- 感覺更像是常規的舊 javascript 導入模塊,將操作為方法調用,直接在store上訪問狀態等。
- 不再mutations
- 與 Vue Devtools 集成
結論
Pinia雖然是Vue生態的新成員,但是事實證明Pinia是最優前途的狀態管理解決方案,具有直觀的API,模塊化,清晰導入來源。
參考文獻
Pinia, an Alternative Vue.js Store
官網
到此這篇關於Vue生態的新成員Pinia的詳細介紹的文章就介紹到這瞭,更多相關Vue Pinia內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Vue狀態管理庫Pinia詳細介紹
- 還在用vuex?來瞭解一下pinia
- JavaScript Pinia代替 Vuex的可行性分析
- 詳解Vue3-pinia狀態管理
- 一文詳解Pinia和Vuex與兩個Vue狀態管理模式