溫馨提示:本文以vue3+vite+ts舉例,vite設定和ts語法側重較少,比較適合有vuex或者vue基礎的小夥伴們兒查閱。
yarn add pinia
npm install pinia
pnpm add pinia
main.ts
中直接引入pinia在 src/main.ts
中引入pinia(根儲存),並傳遞給應用程式。
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 1-建立一個 pinia(根儲存)
import { createPinia } from 'pinia'
const app = createApp(App)
// 2-告訴應用程式,我們將使用pinia
const pinia = createPinia();
// 以外掛形式傳遞給app
app.use(pinia);
app.mount('#app');
.ts
檔案引入pinia在根目錄下新建資料夾,這裡我命名為store
,再在資料夾下新建一個index.ts
檔案(src/store/index.ts
),用以設定和引入pinia。
// 1-建立一個 pinia(根儲存)
import { createPinia } from 'pinia'
// 2-定義pinia範例
const pinia = createPinia();
// 3-暴露pinia範例
export default pinia;
然後在src/main.ts
中使用。
......
import pinia from '@/store/index.ts'
app.use(pinia);
......
其實和方式一沒啥區別,只是為了保持main.ts
檔案整潔,並且方便設定pinia。
pinia與vuex差不多,相比於vuex,少了mutation
和modules
。
pinia建立倉庫,有選項式寫法和組合式寫法。
在根目錄下建立一個資料夾store (src/store
),在store資料夾中可以建立你的倉庫,比如下面我建立了一個名為user的倉庫 (src/store/user.ts
)。
// 選項式寫法
// 1-引入api
import { defineStore } from "pinia";
// 2-定義倉庫
const store = defineStore('user', {
// 3-設定元件共用的狀態,相當於元件的data
state: () => ({
userInfo: {
name: '老劉',
sex: '男',
age: 17,
isStudent: false
},
token: '5201314',
password: '123456',
}),
// 3-設定狀態計算值,相當於元件的computed
getters: {
name: (state) => state.userInfo.name,
sex: (state) => state.userInfo.sex,
},
// 3-設定元件共用的方法,相當於元件的methods
actions: {
addAge() {
this.userInfo.age++;
}
}
});
// 最後別忘了把倉庫暴露出去哦
export default store;
上面的倉庫 (src/store/user.ts
)組合式寫法如下:
// 組合式寫法
// 1-引入pinia的api
import { defineStore } from "pinia";
// 2-引入vue3相關api
import { ref, reactive, computed } from 'vue';
// 3-定義倉庫,注意第二個引數需要傳入一個函數,函數需要返回一個物件!
const store = defineStore('user', () => {
// 4-在這裡面可以像在元件中一樣,使用vue3的API,定義響應式資料
const userInfo = reactive({
name: '老劉',
sex: '男',
age: 17,
isStudent: false
});
const token = ref('5201314');
const password = ref('123456');
// 這裡computed的作用相當於getters
const name = computed(() => userInfo.name);
const sex = computed(() => userInfo.sex);
// 4-還可以定義方法
function addAge() {
userInfo.age++;
}
// 5-然後把需要共用的資料或方法,裝進一個物件,return出去
return {
userInfo,
token,
password,
name,
sex,
addAge
}
});
// 最後別忘了把倉庫暴露出去哦
export default store;
TIP
還可以在倉庫中使用
watch
、watchEffect
等vue3的API喔~。import { ref, reactive, computed, watch } from 'vue'; const store = defineStore('user', () => { const userInfo = reactive({ age: 17, }); // 使用vue3的watch()函數,可以對倉庫狀態進行監聽 watch(() => userInfo.age, (val) => { console.log(val); }); });
完成了上面的工作後,我們就可以在元件中愉快地使用pinia啦!
下面以src/App.vue
作為範例。
<template>
</template>
<script setup lang="ts">
// 1-引入剛剛自定義的倉庫,模組名store 可以自定義
import store from '@/store/user.ts';
// 2-使用倉庫,倉庫範例名userStore 可以自定義
const userStore = store();
</script>
state
和getters
在模板和script中,state和getters可以看作倉庫範例(如userStore
)的屬性,直接加.
存取即可。
<template>
<div>
<!-- 1-存取倉庫的計算屬性getters -->
<span>姓名:{{ userStore.name }}</span>
<span>性別:{{ userStore.sex }}</span>
<!-- 1-存取倉庫的狀態state -->
<span>年齡:{{ userStore.userInfo.age }}</span>
<span>是否學生:{{ userStore.userInfo.isStudent ? '是' : '否' }}</span>
</div>
</template>
<script setup lang="ts">
import store from '@/store/user.ts';
const userStore = store();
// 1-存取state和getters,類似於reactive響應式資料的存取
console.log('userStore', userStore);
console.log('姓名:', userStore.name);
console.log('性別:', userStore.sex);
console.log('年齡:', userStore.userInfo.age);
console.log('是否學生:', userStore.userInfo.isStudent ? '是' : '否');
</script>
userStore Proxy(Object) {$id: 'user', $onAction: ƒ, $patch: ƒ, $reset: ƒ, $subscribe: ƒ, …}[[Handler]]: Object[[Target]]: Object[[IsRevoked]]: false
姓名: 老劉
性別: 男
年齡: 17
是否學生: 否
actions
使用倉庫方法與存取倉庫state類似,倉庫範例後直接加.
呼叫即可。
<template>
<div>
<!-- 按鈕點選,年齡+1 -->
<button @click="onAddAge">年齡+1</button>
<span>年齡:{{ userStore.userInfo.age }}</span>
</div>
</template>
<script setup lang="ts">
import store from '@/store/user.ts';
const userStore = store();
// 按鈕點選觸發
function onAddAge() {
// 1-使用倉庫的actions
userStore.addAge();
}
</script>
state
與vuex不同,pinia支援在元件中直接修改state
。
<template>
<div>
<!-- 按鈕點選,年齡+1 -->
<button @click="onAddAge">年齡+1</button>
<span>年齡:{{ userStore.userInfo.age }}</span>
</div>
</template>
<script setup lang="ts">
import store from '@/store/user.ts';
const userStore = store();
// 按鈕點選觸發
function onAddAge() {
// 1-直接修改state
userStore.userInfo.age++;
}
</script>
actions
進行修改src/store/user.ts
......
const store = defineStore('user', () => {
......
// 1-定義方法
function addAge() {
userInfo.age++;
}
// 2-return出去
return {
addAge
}
});
// 3-匯出倉庫
export default store;
src/App.vue
<template>
<div>
<!-- 按鈕點選,年齡+1 -->
<button @click="onAddAge">年齡+1</button>
<span>年齡:{{ userStore.userInfo.age }}</span>
</div>
</template>
<script setup lang="ts">
import store from '@/store/user.ts';
const userStore = store();
// 按鈕點選觸發
function onAddAge() {
// 4-使用倉庫的方法
userStore.addAge();
}
</script>
通過倉庫範例(如userStore
)的 $patch
方法,可以對state
同時應用多個更改。
<script setup lang="ts">
......
function changeState() {
console.log(userStore.token); // '5201314'
console.log(userStore.password); // '123456'
// $patch()接收一個物件,物件內的屬性是 需要變更的state
// 注意是變更,新增state是無效的!
userStore.$patch({
token: '1024',
password: '654321'
});
console.log(userStore.token); // '1024'
console.log(userStore.password); // '654321'
}
</script>
上面的方法每次進行批次修改都需要傳入一個新物件,有時候使用起來並不方便。下面是另一種寫法,$patch
接受一個函數來批次修改集合內部分物件。(推薦)
<script setup lang="ts">
......
function changeState() {
// 這裡的any是typescript的型別標註,可以不用理會
// 回撥函數的引數state就是 倉庫目前的state
userStore.$patch((state: any) => {
state.token = '1024';
state.password = '654321';
});
}
</script>
通過倉庫範例(如userStore
)的 $state
屬性,來為新物件替換倉庫的整個狀態。
pinia官網提到整體替換state的方法,但並未說明是否保留資料響應式。經筆者實踐,這種方法會丟失資料的響應式,所以不推薦使用。
<script setup lang="ts">
......
function updateStore() {
userStore.$state = {
userInfo: {
name: '老王',
sex: '男',
age: 66,
isStudent: false
}
};
}
</script>
state
通過呼叫倉庫範例上的 $reset()
方法將狀態重置到其初始值。
<script setup lang="ts">
......
function resetStore() {
userStore.$reset();
}
</script>
$reset()
的坑細心的你會發現,倉庫state
並沒有重置,然後你開啟你的的控制檯,你會驚訝地發現它報了這麼一個錯誤:
這時候請你不要慌,先冷靜地看一下報錯資訊。
這裡翻譯一下:Store "user"是使用setup語法構建的,不實現$reset()
。(猜測是pinia的缺陷)
所以,根據報錯資訊,這裡提供下面兩種解決方案。
使用選項式Options API編寫pinia倉庫,並且在元件中不能用script setup語法,要使用setup函數進行開發。
src/store/user.ts
......
// 使用選項式Options API編寫倉庫
const store = defineStore('user', {
// 3-設定元件共用的狀態,相當於元件的data
state: () => ({
userInfo: {
name: '老劉',
sex: '男',
age: 17,
isStudent: false
},
token: '5201314',
password: '123456',
}),
});
export default store;
src/App.vue
<!-- 這裡不能用script setup,否則依然報錯 -->
<script lang="ts">
import store from '@/store/user.ts';
export default {
setup() {
const userStore = store();
function resetStore() {
// 重置state
userStore.$reset();
}
// 把響應式資料或方法return出去
return {
userStore,
resetStore
}
},
}
</script>
$reset()
方法(推薦)原理:自定義pinia外掛(Plugins),利用$patch()
重置整個state
。
在之前建立的pinia組態檔中修改(src/store/index.ts
)。
import { createPinia } from 'pinia';
const pinia = createPinia();
// 1-使用pinia自定義外掛
pinia.use(({ store }) => {
// 2-獲取最開始的State
const initialState = JSON.parse(JSON.stringify(store.$state));
// 3-重寫$reset()方法
store.$reset = () => {
// 4-利用$patch()批次變更state,達到重置state的目的
store.$patch(initialState);
}
});
export default pinia;
推薦使用這種方法,這樣就可以在script setup
中愉快地使用pinia啦!
src/store/index.ts
import { createPinia } from 'pinia';
const pinia = createPinia();
pinia.use(({ store }) => {
const initialState = JSON.parse(JSON.stringify(store.$state));
store.$reset = () => {
store.$patch(initialState);
}
});
export default pinia;
src/store/user.ts
// 組合式寫法
import { defineStore } from "pinia";
import { ref, reactive, computed, watch } from 'vue';
const store = defineStore('user', () => {
const userInfo = reactive({
name: '老劉',
sex: '男',
age: 17,
isStudent: false
});
const token = ref('5201314');
const password = ref('123456');
const name = computed(() => userInfo.name);
const sex = computed(() => userInfo.sex);
watch(() => userInfo.age, (val) => {
console.log(val);
});
function addAge() {
userInfo.age++;
}
return {
userInfo,
token,
password,
name,
sex,
addAge
}
});
export default store;
src/App.vue
<template>
<div>
<button @click="resetStore">重置store</button>
<h1>我是父元件</h1>
<button @click="userStore.userInfo.age++">年齡+1</button>
<span class="marginLeft60">姓名:{{ userStore.name }}</span>
<span class="marginLeft60">性別:{{ userStore.sex }}</span>
<span class="marginLeft60 red">年齡:{{ userStore.userInfo.age }}</span>
<span class="marginLeft60 red">是否學生:{{ userStore.userInfo.isStudent ? '是' : '否' }}</span>
<hr>
<!-- 使用子元件A -->
<son-a />
<hr>
<!-- 使用子元件B -->
<son-b />
<hr>
</div>
</template>
<script setup lang="ts">
// 引入子元件
import SonA from './components/sonA.vue';
import SonB from './components/sonB.vue';
// 1-引入倉庫
import store from '@/store/user.ts';
// 2-使用倉庫
const userStore = store();
// 重置store
function resetStore() {
userStore.$reset();
}
</script>
<style>
.marginLeft60 {
margin-left: 60px;
}
.red {
color: red;
}
</style>
src/components/sonA.vue
<template>
<div>
<h2>我是子元件A</h2>
<button @click="userStore.userInfo.isStudent = true">入學</button>
<span class="marginLeft60">姓名:{{ userStore.name }}</span>
<span class="marginLeft60">性別:{{ userStore.sex }}</span>
<span class="marginLeft60 red">年齡:{{ userStore.userInfo.age }}</span>
<span class="marginLeft60 red">是否學生:{{ userStore.userInfo.isStudent ? '是' : '否' }}</span>
<grandson />
</div>
</template>
<script setup lang="ts">
import Grandson from './grandson.vue';
import store from '@/store/user.ts';
const userStore = store();
</script>
src/components/sonB.vue
<template>
<div>
<h2>我是子元件B</h2>
<button @click="userStore.userInfo.isStudent = false">畢業</button>
<span class="marginLeft60">姓名:{{ userStore.name }}</span>
<span class="marginLeft60">性別:{{ userStore.sex }}</span>
<span class="marginLeft60 red">年齡:{{ userStore.userInfo.age }}</span>
<span class="marginLeft60 red">是否學生:{{ userStore.userInfo.isStudent ? '是' : '否' }}</span>
</div>
</template>
<script setup lang="ts">
import store from '@/store/user.ts';
const userStore = store();
</script>
src/components/grandson.vue
<template>
<div>
<h3>我是孫元件</h3>
<span class="marginLeft60">姓名:{{ userStore.name }}</span>
<span class="marginLeft60">性別:{{ userStore.sex }}</span>
<span class="marginLeft60 red">年齡:{{ userStore.userInfo.age }}</span>
<span class="marginLeft60 red">是否學生:{{ userStore.userInfo.isStudent ? '是' : '否' }}</span>
</div>
</template>
<script setup lang="ts">
import store from '@/store/user.ts';
const userStore = store();
</script>