动态

详情 返回 返回

中後台開發必修課:Vue 項目中 Pinia 與 Router 完全攻略 - 动态 详情

前言

本篇文章主要講解如何來配置 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.tslocale.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:數據源,存儲數據的地方,可以被讀取和修改,本文中是 token
  • getter:計算屬性,Getter 完全等同於 store 的 state 的計算值,一般用於讀取數據,本文中是 getToken
  • actions:方法,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 註冊完後,它會全局註冊 RouterViewRouterLink 組件,啓用 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();

完成!到這一步,你應該可以看到頁面上顯示的文字了

實現頁面:
Pasted image 20241024171052

最終的文件結構是
Pasted image 20241024173649

最後,如果你想要添加更多的路由頁面,只需要在 modules 下新增配置文件即可生效,無須手動引入註冊

瞭解更多

系列專欄地址:GitHub 博客(推薦) | 掘金專欄 | 思否專欄

專欄往期回顧:

  1. 收下這份 Vue + TS + Vite 中後台系統搭建指南,從此不再害怕建項目

交流討論

文章如有錯誤或需要改進之處,歡迎指正

更多高質量文章請關注 GitHub 博客,歡迎 star
user avatar tianmiaogongzuoshi_5ca47d59bef41 头像 cyzf 头像 zaotalk 头像 nihaojob 头像 freeman_tian 头像 qingzhan 头像 dirackeeko 头像 6fafa 头像 razyliang 头像 leexiaohui1997 头像 huajianketang 头像 huichangkudelingdai 头像
点赞 114 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.