前言
本篇文章主要讲解如何来配置 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
:数据源,存储数据的地方,可以被读取和修改,本文中是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
注册完后,它会全局注册 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 博客(推荐) | 掘金专栏 | 思否专栏
专栏往期回顾:
交流讨论
文章如有错误或需要改进之处,欢迎指正
更多高质量文章请关注 GitHub 博客,欢迎 star
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。