Vue 的狀態 (資料) 是靠著 ref
和 reactive
儲存,然後靠著 props
和 emit
在組件中傳遞,可是如果今天專案一大,難免會有需要資料共享的狀況,那時不免要 props
來 props
去、emit
來 emit
去。
針對這個問題,當然也可以選擇使用 Provide-Inject
的方式來傳遞,但其實有更好的做法,就是用 Pinia 統一管理資料。
在 Vue 專案建立的時候其實就有選項問說要不要使用 Pinia 來做管理,如果選了的話,當專案建立完會在 /src 下發現一個 stores 資料夾,同時在 main.js 中也會發現 Pinia 自動被引入:
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
如果是舊專案要引入 Pinia,也可以透過 npm install pinia
的方式下載安裝,在自己手動修改 main.js 的內容。
新專案會在 /src/stores 下預設建立好一個 counter.js 作為範例,它就是一個 store。
Store 負責儲存可供全域使用的狀態及邏輯,換句話說,它可以儲存 data、computed 以及 method。
定義一個 store,必須先從 pinia 引入 defineStore
,命名的時候通常以 "use 開頭 + 這個 store 主要幹的事 + store 結尾"。defineStore
接受兩個參數,一個是 store id,大可直接放入去掉 use 的命名;第二個是一個 arrow function,裡面就是儲存資料或邏輯的地方。
現在來看一下 pinia 預先在專案裡給我們的例子:
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
會發現 store 裡的資料或邏輯寫法跟在 <script setup>
裡寫的基本一樣,這是組合式的寫法,較早一點的選項式寫法更簡便易懂。然後透過 return
把資料或邏輯給拋出來給全域使用。
在組件中我們可以直接 import
要用的 store,並把內容除存在某變數之中 (xxStore 之類的),之後可以在組件中調用 xxStore 內部的資料或邏輯。
App.vue:
<script setup>
import {useCounterStore} from '@/stores/counter'
const store = useCounterStore()
</script>
<template>
<h1>{{ store.count }}</h1>
<button @click="store.increment">Add</button>
</template>
我們可以看到畫面渲染出 store 中定義的 count 並且按鈕也可以運作。
Store 的解構一句你想解構的是資料還是方法有不同做法。如果單純是方法可以直接解構,但如果是響應式資料 (包括 computed
) 則要使用 storeToRefs
來做提取,看一下官方的例子,來實做看看:
App.vue:
<script setup>
import { storeToRefs } from 'pinia'
import {useCounterStore} from '@/stores/counter'
const store = useCounterStore()
const {count, doubleCount} = storeToRefs(store)
const {increment} = store
</script>
<template>
<h1>{{ count }}</h1>
<button @click="increment">Add</button>
</template>
畫面一樣可以運作,解構成功!
我們可以在 store 中定義 $reset
方法來讓狀態重置:
Counter.js:
// 添加 reset 方法
const $reset = () => count.value = 0
return { count, doubleCount, increment, $reset }
App.vue:
<script setup>
import {useCounterStore} from '@/stores/counter'
const store = useCounterStore()
</script>
<template>
<h1>{{ store.count }}</h1>
<button @click="store.increment">Add</button>
<button @click="store.$reset">Reset to 0</button>
</template>
當我們點擊重置按鈕時,畫面數字就會歸零了。
存在 store 中的數據有三種修改方式:
<button @click="store.count++">Add</button>
<button @click="store.increment">Add</button>
$patch
:const addCount = () =>{
store.$patch(state =>{
state.count++
})
}
<button @click="addCount">Add</button>
可不可以用 watch
?當然可以,但 pinia 提供了屬於它的 $subscribe
用法。
Subscribe
接收兩個參數,一個是 mutation,一個是監聽對象。Mutation console.log
出來又有三個數性值:
我們在 App.vue 中新增一個 subscribe
:
store.$subscribe((mutation, state) =>{
console.log('Something changed!')
})
當我們點擊按鈕後會 console.log
出 Something changed!
我們也可以用 watch
做監聽,但記得添加深層監聽:
watch(store, state =>{
console.log(`watch here`, state)
}, {deep : true})
如同監聽數據一般,Vue 可以用 $onAction
監聽方法的使用,比方說我要監聽 increment
這個函式的執行時間:
store.$onAction(({ name, after }) => {
if (name === 'increment') {
const startTime = Date.now();
after(() => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.`
);
});
}
})
打開 devtool 就可以看到監測到的執行時間。