pinia是一個vue的狀态存儲庫,你可以使用它來存儲、共享一些跨元件或者頁面的資料,使用起來和vuex非常類似。pina相對Vuex來說,更好的ts支援和代碼自動補全功能。本篇随筆介紹pinia的基礎用法以及持久化存儲的一些用法,供參考學習。
pinia在2019年11月開始時候是一個實驗項目,目的就是重新設計一個與組合API比對的vue狀态存儲。基本原則和原來還是一樣的,pinia同時支援vue2和vue3,且不要求你必須使用Vue3的組合API。不管是使用vue2或者vue3,pinia的API是相同的,文檔是基于vue3寫的。
Pinia 是 Vuex4 的更新版,也就是 Vuex5; Pinia 極大的簡化了Vuex的使用,是 Vue3的新的狀态管理工具;Pinia 對 ts的支援更好,性能更優, 體積更小,無 mutations,可用于 Vue2 和 Vue3;Pinia支援Vue Devtools、 子產品熱更新和服務端渲染。
1、pinia的安裝和使用
安裝pinia(https://pinia.vuejs.org/)
npm install pinia
在main.j或者main.ts中引入使用
import { createPinia } from 'pinia'
app.use(createPinia())
下面就是使用pinia的一個例子。這樣你就建立了一個狀态存儲。
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
// 也可以這樣定義狀态
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++
},
},
})
在元件中使用:
import { useCounterStore } from '@/stores/counter'
export default {
setup() {
const counter = useCounterStore()
counter.count++
// 編輯器會有代碼提示
counter.$patch({ count: counter.count + 1 })
// 也可以使用action來代替
counter.increment()
},
}
如果你不是很喜歡setup函數群組合API,pinia也有類似vuex的map的功能。你可以用上面的方式定義你的store,但是使用時用mapStores(), mapState(),或者 mapActions():
const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
}
}
})
const useUserStore = defineStore('user', {
// ...
})
export default {
computed: {
// 其他計算屬性
// ...
// 可以使用 this.counterStore 和 this.userStore擷取
...mapStores(useCounterStore, useUserStore)
// 可以使用 this.count 和this.double擷取
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
// 可以使用 this.increment()調用
...mapActions(useCounterStore, ['increment']),
},
}
與vue4之前的版本相比,pinia的API是有很多不同的,即:
- 去掉了mutation。因為好多人認為mutation是多餘的。以前它友善devtools內建,現在這不是個問題了。
- 不用在寫複雜的ts類型包裝,所有的都是有類型的,API設計的都是盡量符合ts的類型推斷
- 不再使用一個莫名其妙的字元串了,隻需要導入一個函數,調用他們就行了,同時還有代碼自動補全
- 不需要動态添加store了,因為它們現在本來就是動态。如果你想,你随時可以手動去寫一個store。
- 沒有複雜的嵌套子產品了。你仍然可以在一個store中導入其他的store來實作嵌套子產品,但是pinia還是推薦使用一個扁平的結構。但是即使你使用循環依賴也沒關系。
- 不再需要命名空間了。因為現在store本來就是扁平結構了。你也可以了解為所有的store本來就有命名空間了。
你的應用中的全局資料需要儲存在store中。在很多地方你都要使用這些資料,比如說,使用者資訊需要在導航欄中顯示,也需要在個人中心顯示。還有些資料,需要暫存起來,比如一個需要分好幾頁填寫的表單。
在pinia中,store是通過defineStore()方法定義的,它的第一個參數就是一個唯一的名字:
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
// other options...
})
上面隻是定義了store,在setup函數中調用了useStore()時,才會建立store:
import { useStore } from '@/stores/counter'
export default {
setup() {
const store = useStore()
return {
// 你可以傳回store這個對象,然後就可以在template中使用了
store,
}
},
}
在store執行個體化以後,你就可以調用到store中定義的state、getters和actions了。為了讓解構的值還保持響應式,你需要用到storeToRefs()方法。它會給響應式的資料建立ref。
import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const store = useStore()
// `name` 和 `doubleCount` 是響應式的
// 插件增加的屬性也會建立ref
// 但是會自動跳過action或者不是響應性的屬性
const { name, doubleCount } = storeToRefs(store)
return {
name,
doubleCount
}
},
})
預設情況下,你可以在store執行個體上直接擷取或者修改state:
const store = useStore()
store.counter++
也可以調用$reset()方法來把state恢複為初始值:
const store = useStore()
store.$reset()
除了直接修改store裡的值store.counter++,你也可以是用$patch方法。你可以同時修改多個值:
store.$patch({
counter: store.counter + 1,
name: 'Abalam',
})
或者$patch接收一個函數作為參數,來簡化改變數組的寫法:
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
2、pinia的持久化存儲處理
你可以用$subscribe()來偵聽state的改變,持久化一般存儲在localStorage和sessionStorage。
localStorage和sessionStorage差别
localStorage和sessionStorage一樣都是用來存儲用戶端臨時資訊的對象。
他們均隻能存儲字元串類型的對象(雖然規範中可以存儲其他原生類型的對象,但是目前為止沒有浏覽器對其進行實作)。
localStorage生命周期是永久,這意味着除非使用者顯示在浏覽器提供的UI上清除localStorage資訊,否則這些資訊将永遠存在。
sessionStorage生命周期為目前視窗或标簽頁,一旦視窗或标簽頁被永久關閉了,那麼所有通過sessionStorage存儲的資料也就被清空了。
不同浏覽器無法共享localStorage或sessionStorage中的資訊。相同浏覽器的不同頁面間可以共享相同的 localStorage(頁面屬于相同域名和端口),但是不同頁面或标簽頁間無法共享sessionStorage的資訊。這裡需要注意的是,頁面及标 簽頁僅指頂級視窗,如果一個标簽頁包含多個iframe标簽且他們屬于同源頁面,那麼他們之間是可以共享sessionStorage的。
JSON對象提供的parse和stringify将其他資料類型轉化成字元串,再存儲到storage中就可以了,操作的方式:
存:
var obj = {"name":"xiaoming","age":"16"}
localStorage.setItem("userInfo",JSON.stringify(obj));
取:
var user = JSON.parse(localStorage.getItem("userInfo"))
删除:
localStorage.remove("userInfo);
清空:
localStorage.clear();
pnia 使用訂閱機制subscribe來實作資料的持久化存儲的代碼如下所示。
const instance = useMainStore();
// 訂閱資料變化,變化時存儲 instance.$id 這是storeId
instance.$subscribe((mutation, state) => {
localStorage.setItem(instance.$id, JSON.stringify(state));
});
//init 初始的時候擷取
const val = localStorage.getItem(instance.$id);
if (val) {
instance.$state = JSON.parse(val);
}
也可以通過watch實作
watch(
pinia.state,
(state) => {
// persist the whole state to the local storage whenever it changes
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
但是需要注意,這種方式持久化會提示pinia未安裝挂載,是以需要在pinia挂載後再調用,這裡可以将它封裝成方法導出,在挂載後調用
xport const initStore = () => {
const instance = useMainStore();
// 訂閱資料變化,變化時存儲 instance.$id 這是storeId
instance.$subscribe((mutation, state) => {
localStorage.setItem(instance.$id, JSON.stringify(state));
});
//init 初始的時候擷取
const val = localStorage.getItem(instance.$id);
if (val) {
instance.$state = JSON.parse(val);
}
}
預設情況下,state偵聽會群組件綁定在一起(如果store是在元件的setup中)。這意味着,當元件解除安裝時,偵聽會自動被移除。如果你需要在元件被解除安裝時,偵聽仍然保持,需要給$subscribe()方法傳遞第二個參數true:
export default {
setup() {
const someStore = useSomeStore()
// 元件解除安裝後,偵聽也會有
someStore.$subscribe(callback, true)
// ...
},
}
或者watch狀态的變化
watch(
pinia.state,
(state) => {
// 在state改變時,儲存在localStorage中
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
3、使用pinia插件持久化存儲
pinia plugin persist官方網站:pinia-plugin-persist
持久化存儲也可以通過安裝插件的方式,安裝 pinia-plugin-persist 來實作。
npm i pinia-plugin-persist --save
使用main.js
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'
const store = createPinia()
store.use(piniaPluginPersist)
createApp(App).use(store).mount('#app')
在對應的store中開啟,資料預設存在 sessionStorage 裡,并且會以 storeId 作為 key
import { defineStore } from 'pinia'
// 'main' 是storeId
export const useMainStore = defineStore('main', {
state: () => ({
counter: 2,
name: 'Eduardo',
isAdmin: true
}),
// ……
// 開啟資料緩存
persist: {
enabled: true
}
})
如果需要自定義key和存儲位置,則修改參數即可。
persist: {
enabled: true,
strategies: [ //使用插件自定義存儲
{
key: 'settings', // key可以自己定義,不填的話預設就是這個store的ID
storage: localStorage,
}
]
},
4、在實際項目中使用pinia
一般項目開發,實際上存儲的内容會比較多,可能根據不同的鍵值子產品進行區分,是以把它們放在一個store/modules裡面,友善的使用引用它來存取設定資料即可。
我們這裡簡單以一個settings的配置資訊進行介紹,其中index.ts是一個統一的建立pinia的對象并挂接到全局App上的。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiATN381dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iM2MzM0MGZwMDNmVzMhVTNyYzXxUTNwADMzEzLcRDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
其中index.ts的代碼如下所示。
import type { App } from "vue";
import { createPinia } from "pinia";
import piniaPluginPersist from 'pinia-plugin-persist';//使用插件持久化
const store = createPinia();
store.use(piniaPluginPersist) //使用插件持久化
export function setupStore(app: App<Element>) {
app.use(store);
}
export { store };
是以在main.js裡面引入并挂接pinia即可。
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import 'normalize.css' // css初始化
import App from './App.vue'
import { setupStore } from "/@/store";
const app = createApp(App)
setupStore(app)
app.use(ElementPlus)
app.mount('#app')
這樣我們就可以再次定義一個子產品化的配置資訊,以便于管理存儲各種不同類型的内容。
如下面我們定義一個程式配置資訊setttings.ts
import { defineStore } from "pinia";
import { store } from "/@/store";
export type settingType = {
title: string;
fixedHeader: boolean;
hiddenSideBar: boolean;
};
export const useSettingStore = defineStore({
id: "settings",
state: (): settingType => ({
title: "Vue3 + TypeScript + Element",
fixedHeader: false,
hiddenSideBar: false
}),
persist: {
enabled: true,
strategies: [ //使用插件自定義存儲
{
key: 'settings', // key可以自己定義,不填的話預設就是這個store的ID
storage: localStorage,
}
]
},
getters: {
getTitle() {
return this.title;
},
getFixedHeader() {
return this.fixedHeader;
},
getHiddenSideBar() {
return this.HiddenSideBar;
}
},
actions: {
CHANGE_SETTING({ key, value }) {
// eslint-disable-next-line no-prototype-builtins
if (this.hasOwnProperty(key)) {
this[key] = value;
}
},
changeSetting(data) {
this.CHANGE_SETTING(data);
}
}
});
export function useSettingStoreHook() {
return useSettingStore(store);
}
然後在元件視圖vue或者app.vue中使用即可
<script lang="ts">
import { defineComponent } from "vue";
import { useSettingStoreHook } from "/@/store/modules/settings";
import { storeToRefs } from "pinia";
export default defineComponent({
name: "app",
components: {
},
setup() {
const store = useSettingStoreHook();
const { fixedHeader, title } = storeToRefs(store);
return {
fixedHeader,
title,
};
},
methods: {
setTitle() {
this.title = "Vue3 + TypeScript + Element + Edit";
console.log(this.title);
},
},
});
</script>
檢視資料修改後,存儲在本地存儲空間中的内容,如下所示。