前言
本篇文章主要講解如何來配置 Pinia 和 Vue Router
本文也是《通俗易懂的中後台系統建設指南》系列的第二篇文章,該系列旨在告訴你如何來構建一個優秀的中後台管理系統
寫在前面
路由(Router)和狀態管理(Vuex、Pinia)是 Vue 項目中的常客。基本上在 Vue 的項目中,我們構建一個 Web 應用都離不開它們,如果你是 Vue2 的用户,那麼你對它們不會陌生
如果你是跟着本系列第一篇搭建文章的話,那麼此時你應該擁有一個基礎性的項目,搭好路由和狀態管理這兩個板塊,是一塊基石,下面我們講解如何配置它們。
Pinia 狀態管理
Pinia 是 Vue 的專屬狀態管理庫,它允許你跨組件或頁面共享狀態
在這之前,我們用的是 Vuex
如官網所説,與 Vuex 相比,Pinia 不僅提供了一個更簡單的 API,也提供了符合組合式 API 風格的 API,最重要的是,搭配 TypeScript 一起使用時有非常可靠的類型推斷支持
Pinia 數據持久化
持久化,顧名思義,保持數據持久不消失
要實現 pinia 數據持久化,我們需要藉助 pinia-plugin-persistedstate 插件
持久化的需求體現在哪裏?
- 用户偏好設置,比如主題、語言、個性等,將這些偏好持久化可以確保用户在下次打開時仍然保持個性設置
- 用户認證態,即保存用户的登錄數據,以便下次打開此應用時不需要重複登錄
- 性能優化,對於一些需要頻繁訪問但很少改變的數據,將其持久化到本地存儲可以減少網絡請求
Pinia 安裝
安裝 Pinia 及持久化依賴 pinia-plugin-persistedstate
pnpm add pinia pinia-plugin-persistedstate
初始化
安裝完成後,我們創建 src/store 目錄,表示這個目錄下的內容都是與存儲相關
然後在目錄下新建 init.ts 文件和 modules 文件夾:
init.ts作為基礎文件,用於註冊 Pinia 和 Pinia 的基本配置modules文件夾下存放的是 Store 文件,比如user.ts、locale.ts等存儲文件index.ts用於導出modules下的全部存儲文件
現在來配置 Pinia 和持久化,在 init.ts 中寫入以下內容
import { createPinia } from 'pinia';
import { App } from 'vue';
import { createPersistedState } from 'pinia-plugin-persistedstate';
// 實例
const store = createPinia();
// 配置持久化
store.use(
createPersistedState({
key: (id) => `__APP__${id}__`.toUpperCase(),
}),
);
/**
* 初始化 Pinia
*/
const initStore = (app: App<Element>) => {
return app.use(store);
};
export { store, initStore };
在 main.ts 文件中進行註冊
import './styles/tailwind.css';
import { createApp } from 'vue'
import { initStore } from './store/init';
import App from './App.vue'
async function bootstrapApp() {
const app = createApp(App);
initStore(app);
app.mount('#app');
}
bootstrapApp()
存儲文件的配置
初始化之後,我們來進行存儲文件的配置,在 modules 文件夾下新建一個 user.ts 文件,寫入以下內容
import { computed, ref } from 'vue';
import { store } from '../init';
import { acceptHMRUpdate, defineStore } from 'pinia';
const createUserStore = defineStore('user', () => {
const token = ref<string>('')
/** 獲取Token */
const getToken = computed(() => {
return token.value;
});
/** 設置Token */
const setToken = (value: string) => {
return token.value = value
}
return { getToken, setToken };
});
import.meta.hot && import.meta.hot.accept(acceptHMRUpdate(createUserStore, import.meta.hot));
/**
* 注入 Pinia 實例,使其能在組件外使用
* @see:https://pinia.vuejs.org/zh/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component
*/
export const useUserStore = () => {
return createUserStore(store);
};
上面的代碼定義了一個 user 的存儲文件,用來存儲用户信息,可以是用户基本信息、token、權限、角色等,其中主要的配置項為:
state:數據源,存儲數據的地方,可以被讀取和修改,本文中是tokengetter:計算屬性,Getter 完全等同於 store 的 state 的計算值,一般用於讀取數據,本文中是getTokenactions:方法,Actions相當於組件中的 method,可以包含任意的異步操作或同步操作,用於修改 state 或者定義業務邏輯,本文中是setToken
上面使用的語法是 Setup Store,即組合式寫法
對於使用過 Vuex 的用户來説,你肯定會熟悉選項式寫法 ,即 Option Store,兩種寫法沒有絕對的好壞之分,Option Store 更容易使用,而 Setup Store 更靈活和強大
Pinia Error
在上面的 user.ts 文件底部,有這麼一段代碼:
export const useUserStore = () => {
return createUserStore(store);
};
為什麼導出一個 useUserStore 方法,而不是直接導出 createUserStore 呢?
其實,是用於解決以下報錯問題:
在官網文檔中,有這樣一段話
Pinia store 依靠pinia實例在所有調用中共享同一個 store 實例。大多數時候,只需調用你定義的useStore()函數,完全開箱即用。例如,在setup()中,你不需要再做任何事情。但在組件之外,情況就有點不同了。 實際上,useStore()給你的app自動注入了pinia實例。這意味着,如果pinia實例不能自動注入,你必須手動提供給useStore()函數
針對這種情況,我們就可以創建一個包裝函數,就如 useUserStore()
這個函數在內部調用 createUserStore(store),這裏的 store 是 Pinia 實例,這樣做可以解決
- Pinia 實例未被初始化的問題
- 在組件外使用
store的問題
這樣,我們就可以在整個應用中一致地使用 useUserStore() 方法
統一引入
注意,此章節是可選的
上面篇章中,我們使用 init.ts 註冊配置了 Pinia,又在 modules 創建了一個 useUserStore,其實到這一步,你已經可以使用 useUserStore 了,比如:
這裏的@代表src目錄,需要在Vite中配置
<script setup lang="ts">
import { useUserStore } from '@/store/modules/user';
defineOptions({
name: 'Test',
});
const userStore = useUserStore();
console.log(userStore.getToken);
</script>
<template>
<div>這裏是測試</div>
</template>
但我們可以做的更好點,比如創建一個 index.ts 文件,並導出 modules 下所有存儲,方便後續的引入使用,操作如下:
export * from './modules/user';
// ...引入更多
那麼需要使用的時候,路徑可以是這樣寫:
// import { useUserStore } from '@/store/modules/user'; (之前寫法)
import { useUserStore } from '@/store';
Router 路由
Vue Router 是 Vue.js 的官方路由。它與 Vue.js 核心深度集成,讓用 Vue.js 構建單頁應用 (SPA) 變得輕而易舉
如果你是使用過 Vue2 的用户,那麼你應該對 Vue Router 並不陌生,我們先來安裝它
pnpm add vue-router@4
然後來對 Vue Router 進行配置,與上述的 Pinia 配置類似,創建一個 src/router 目錄,表示這個目錄下的內容都是與路由相關
裏面新建 init.ts 文件、index.ts 文件 和 modules 文件夾
init.ts用於 Router 的註冊和基本配置index.ts用於對modules下的路由做處理,比如自動導出modules文件夾下存放的是全部路由配置文件
具體配置
我們先來基本配置一下 Vue Router,在 init.ts 中寫入以下內容:
import type { RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import type { App } from 'vue';
import { routes } from './index';
export const router = createRouter({
history: createWebHashHistory(),
routes: routes as RouteRecordRaw[],
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
});
/** 初始化路由 */
export function initRouter(app: App<Element>) {
app.use(router);
}
上面內容通過 createRouter() 函數創建了一個路由器實例,函數內有一些配置項:
1) history 配置路由模式,分為三種模式:
createMemoryHistory()(不推薦),參閲 Menory-模式createWebHashHistory(),Hash 模式,在URL上會有一個哈希字符#,參閲Hash-模式createWebHistory()(推薦),HTML5 模式,URL上無多餘字符,參閲HTML5-模式
import { createMemoryHistory, createWebHashHistory, createWebHistory } from 'vue-router'
2) routes 路由集,所有路由在這裏註冊(在動態路由中也可以通過實例上的方法 addRoute 來註冊)
3) strict,控制路由的路徑最後是否包含斜線 /,即是否進行嚴格匹配
4) scrollBehavior,定義滾動行為,參閲滾動行為
當 createRouter 註冊完後,它會全局註冊 RouterView,RouterLink 組件,啓用 useRouter() 和 useRoute() 組合式函數等操作,請參閲 註冊路由器插件
最後導出一個 initRouter() 函數,用於註冊路由器
這時候,你會報 找不到模塊“./index”或其相應的類型聲明 問題,我們接着來配置 index.ts:
index.ts
import type { RouteRecordRaw } from 'vue-router';
/** 基礎路由 */
const basicRoutes: Record<string, any> = import.meta.glob(['./modules/basic/**/*.ts'], {
eager: true,
});
const routes: RouteRecordRaw[] = [];
Object.keys(basicRoutes).forEach((key) => {
routes.push(basicRoutes[key].default);
});
export { routes };
這裏的 basicRoutes 會自動導入 modules/basic 下所有 TS 文件,包括嵌套的深度文件,核心實現在於 Vite 提供的 import.meta.glob 函數,參閲 Glob導入
什麼,你的modules目錄下沒有basic?是的,確實沒有,請接着往下操作
接下來配置初始的頁面,在 src 目錄下新建一個 views 文件夾,裏面存放的是頁面文件
在剛剛新建的 views 中新建一個 home 文件夾,裏面加入 index.vue ,代表路由初始化的頁面
index.vue
<script setup lang="ts">
defineOptions({
name: 'Home',
});
</script>
<template>
<div><span class="title">這裏是Home頁面</span></div>
</template>
<style scoped lang="scss">
.title{
font-size: 3rem;
}
</style>
記得把 App.vue 文件的默認內容清理並寫入 RouterView,表示在這裏渲染路由組件
到目前還沒有在main.ts中註冊路由,所以RouterView不會起效
App.vue
<script setup lang="ts"></script>
<template>
<RouterView />
</template>
<style scoped></style>
寫完了這個頁面文件,我們就可以回頭接着路由下配置它,在 modules 目錄下新增一個 basic 文件夾,表示基礎路由的存放地,然後新建一個 home.ts,寫入以下內容
import type { RouteRecordRaw } from 'vue-router';
const Home: RouteRecordRaw = {
path: '/',
redirect: '/home',
children: [
{
path: '/home',
name: 'Home',
component: () => import('@/views/home/index.vue'),
},
],
};
export default Home;
最後一步,在 main.ts 中註冊路由
import { createApp } from 'vue';
import { initStore } from '@/store/init';
import { initRouter } from '@/router/init';
import App from './App.vue';
async function bootstrapApp() {
const app = createApp(App);
initStore(app);
initRouter(app);
app.mount('#app');
}
bootstrapApp();
完成!到這一步,你應該可以看到頁面上顯示的文字了
實現頁面:
最終的文件結構是
最後,如果你想要添加更多的路由頁面,只需要在 modules 下新增配置文件即可生效,無須手動引入註冊
瞭解更多
系列專欄地址:GitHub 博客(推薦) | 掘金專欄 | 思否專欄
專欄往期回顧:
- 收下這份 Vue + TS + Vite 中後台系統搭建指南,從此不再害怕建項目
交流討論
文章如有錯誤或需要改進之處,歡迎指正
更多高質量文章請關注 GitHub 博客,歡迎 star