其他章節請看:
vue3 快速入門 系列
Pinia
vue3 狀態管理這裡選擇 pinia。
雖然 vuex4 已支援 Vue 3 的 Composition API,但是 vue3 官網推薦新的應用使用 pinia —— vue3 pinia
集中式狀態管理
redux、mobx、vuex、pinia都是集中式狀態管理工具。與之對應的就是分散式。
Pinia 符合直覺
的 Vue.js 狀態管理庫 甚至讓你忘記正在使用的是一個狀態庫 —— 官網
安裝 pinia 環境
首先下載安裝包:
PS hello_vue3> npm i pinia
added 2 packages, and audited 71 packages in 11s
10 packages are looking for funding
run `npm fund` for details
1 moderate severity vulnerability
To address all issues, run:
npm audit fix
Run `npm audit` for details.
"dependencies": {
"pinia": "^2.1.7",
"vue": "^3.4.15",
"vue-router": "^4.3.0"
},
在 main.ts 中依次:引入、建立和安裝 pinia,在瀏覽器 vue 開發者工具中就能看到 pinia(一個菠蘿圖示)。
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
// 引入
import { createPinia } from 'pinia'
const app = createApp(App)
// 建立
const pinia = createPinia()
app.use(router)
// 安裝:就像安裝 vue-router 一樣使用
app.use(pinia)
app.mount('#app')
有時這個菠蘿
沒出現,可以關閉瀏覽器或重啟服務。
Tip: 詳細請看 pinia 安裝官網
第一個示例
vuex 的核心概念有 State、Getters、Mutations、Actions和Modules。其中 State 是資料,我們不能直接修改資料。
pinia 比 vuex 更輕量,更易使用。比如拿到資料後就能直接改,符合直覺
。
請看示例:
pinia 的資料從專案目錄上說,會放在 store 資料夾中。
通常我們會對狀態進行分類,比如使用者相關的資料放在 store/user.ts 中:
// src/store/user.ts
import { defineStore } from 'pinia'
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同時以 `use` 開頭且以 `Store` 結尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一個引數是你的應用中 Store 的唯一 ID。 Pinia 將用它來連線 store 和 devtools
export const useUserStore = defineStore('user', {
// actions 裡面放一個一個的方法,用於響應元件中的動作
actions: {
changeNameAndAge() {
// this Proxy(Object)
// 裡面有 $state。在 vue2 中有 $watch、$on等以$開頭的都是給程式設計師用的例項方法
console.log('this', this);
// 沒必要透過 $state,直接訪問即可
this.name += '~'
this.$state.age += 1
}
},
state: () => {
return {
name: 'peng',
age: 18,
}
},
})
透過 defineStore 定義一個 store,第一個引數是 store 的id,命名建議規範,例如使用檔名,匯出方式這裡選擇分別匯出,匯出的名字使用 use+user+store。
state 是一個函式,返回的就是資料
actions 中是一個一個的方法,但不需要像 vuex 中需用 dispatch 觸發。
接著在需要使用狀態的地方使用。讀取狀態的方式有2種,修改狀態的方式有3種:
// Home.vue
<template>
<div>
<!-- 讀取方式1 -->
<p>{{ userStore.name }}</p>
<!-- 讀取方式2。方式1更方便 -->
<p>{{ userStore.$state.age }}</p>
<p><button @click="changeNameAndAge">修改方式1:change age and name</button></p>
<p><button @click="changeNameAndAge2">修改方式2:change age and name</button></p>
<p><button @click="changeNameAndAge3">修改方式3:change age and name</button></p>
</div>
</template>
<script lang="ts" setup name="App">
// 寫 '@/store/user.ts' vscode 報錯:An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled
// 去掉 .ts 即可
import {useUserStore} from '@/store/user'
const userStore = useUserStore()
// userStore: Proxy(Object)
console.log('userStore: ', userStore);
function changeName() {
// 修改資料方式1:直接運算元據
// vue2中的vuex必須透過mutation更新資料,不能直接修改資料
userStore.name += '~'
}
function changeNameAndAge() {
userStore.$state.name += '~'
userStore.$state.age += 1
}
function changeNameAndAge2() {
// $patch 用於批次修改
// patch 中文“碎片”,比如age 就是 pinia 中一個資料碎片
userStore.$patch({
name: userStore.name + '~',
age: userStore.age + 1
})
}
function changeNameAndAge3() {
// 呼叫 actions
userStore.changeNameAndAge()
}
</script>
Tip: changeNameAndAge 會觸發2次修改,而 changeNameAndAge2 使用 $patch 會進行批次修改,從開發者時間線中看到,只執行1次。如果很多資料同時修改,推薦使用 patch。
優雅的讀取資料
前面我們是這麼讀取 store 中資料:
<p>{{ userStore.name }}</p>
const userStore = useUserStore()
如果需要讀取的資料太多,在模板中就會出現很多 userStore,於是我們想到用 toRefs 解構解決。就像這樣:
<p>優雅的讀:{{ name }}</p>
import {toRefs} from 'vue'
const userStore = useUserStore()
const {name} = toRefs(userStore)
但是 toRefs(userStore) 太重,透過console.log(toRefs(userStore))
你會發現toRefs將 store 所有屬性(包括方法)都轉成 ref,其實我們只需要將資料轉成 ref 即可。
pinia 也想到了這個問題,於是可以用 storeToRefs 替代。就像這樣:
<p>優雅的讀:{{ name }}</p>
import {storeToRefs} from 'pinia'
const userStore = useUserStore()
const {name} = storeToRefs(userStore)
// storeToRefs(userStore): {name: ObjectRefImpl, age: ObjectRefImpl}
console.log('storeToRefs(userStore): ', storeToRefs(userStore));
// toRefs(userStore): {$id: ObjectRefImpl, $onAction: ObjectRefImpl, $patch: ObjectRefImpl, $reset: ObjectRefImpl, $subscribe: ObjectRefImpl, …}
console.log('toRefs(userStore): ', toRefs(userStore));
Getters
和 vuex 中 Getters 作用相同,用法類似。
這裡用了兩種方式定義 getters:
state: () => {
return {
name: 'Peng',
age: 18,
}
},
getters: {
// 推薦使用箭頭函式,引數會傳入 state
bigName: (state) => state.name.toLocaleUpperCase(),
// 如果需要訪問其他 getters 屬性,可以透過非箭頭函式,透過 this 訪問
lowerName2(): string{
return this.bigName.toLocaleLowerCase()
}
},
資料取得的方式和 state 相同:
<p>優雅的讀:{{ name }}</p>
<p>bigName:{{ bigName }}</p>
<p>lowerName2:{{ lowerName2 }}</p>
const userStore = useUserStore()
const {name, bigName, lowerName2} = storeToRefs(userStore)
Tip:詳細請看 pinia getters
訂閱
類似於 Vuex 的 subscribe 方法,你可以透過 store 的 $subscribe() 方法偵聽 state 及其變化
只要 userStore 中的資料變化了,函式就會被呼叫,我們通常關心第二個引數:
// 只要 userStore 資料變化,這個
userStore.$subscribe((mutation, state) => {
// {storeId: 'user', type: 'direct', events: {…}}
console.log('mutation: ', mutation);
// Proxy(Object) {name: 'Peng~', age: 19}
console.log('state: ', state);
// 每當狀態發生變化時,將整個 state 持久化到本地儲存。
localStorage.setItem('userStore', JSON.stringify(state))
})
我們可以將 state 儲存到本地,這樣就可以實現頁面重新整理,狀態不丟失。
Tip: 細節請看 訂閱 state。
組合式寫法
目前 actions state 寫法屬於宣告式的:
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
actions: {
changeNameAndAge() {
this.name += '~'
this.$state.age += 1
}
},
state: () => {
return {
name: 'Peng',
age: 18,
}
},
getters: {
bigName: (state) => state.name.toLocaleUpperCase(),
lowerName2(): string{
return this.bigName.toLocaleLowerCase()
}
},
})
將其改成組合式。程式碼如下:
import { defineStore } from 'pinia';
import {ref, computed} from 'vue'
export const useUserStore = defineStore('user', () => {
// 資料用 ref 或 reactive 定義
const name = ref('Peng')
const age = ref(18)
// getters 用計算屬性
const bigName = computed(() => name.value.toLocaleUpperCase())
const lowerName2 = computed(() => bigName.value.toLocaleLowerCase())
// actions 用方法定義
function changeNameAndAge() {
name.value += '~';
age.value += 1;
}
// 最後必須暴露出去
return {
// vscode 中資料一個顏色、方法另一個顏色
name,
age,
bigName,
lowerName2,
changeNameAndAge,
};
});
Tip:組合式寫法更靈活(請看 組合式 Store),層級少,但必須返回。具體如何選擇自行決定。
擴充套件
ref 資料要不要 .value
const a = reactive({
x: 1,
y: 2,
z: ref(3)
})
const b = ref(4)
console.log(a.x)
// ref 如果在裡面,則不需要拆包
console.log(a.z)
console.log(b.value)
讀取響應式物件中的 ref 不需要 .value
其他章節請看:
vue3 快速入門 系列