ts+vue3+vite+pinia+vue-router 踩坑合集(持续更新中...)

前言

  • 抽空整理练手项目升级后的一些坑点; 各位项目中有问题遇到困难也可以留言;我会尽快回复! 后续还会继续更新问题点!

1. 预处理器

安装sass以及配置全局scss

npm install node-sass
npm install sass-loader

node 版本不能过高; 目前成功版本为10.15.0; 可通过安装"n"来管理node版本

配置全局mixin.scss
根目录找到vite.config.js

// vite.config.js 
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
...
  plugins: [
    vue(),
  ],
  resolve: {
    // 配置别名
    alias: [
      { find: '@', replacement: resolve(__dirname, './src') },
      { find: 'views', replacement: resolve(__dirname, './src/views') },
    ],
  },
  css: {
    // 添加全局mixin.scss
    preprocessorOptions: {
      scss: {
        additionalData: '@import "@/style/libs.scss";',
      },
    },
  },
});
scss参与计算 (内置模块)

原来的写法直接计算是不行的, 需要使用内置模块math.div()方法参与计算

//libs.scss
//使用math 需要先声明使用; 添加在文件顶部
@use "sass:math" 

// 原来的写法
border-width: $w/2;

// 新写法
border-width: math.div($w/2)

新写法; 切记math需要声明使用(https://sass-lang.com/documentation/modules); 不然就会提示:
Error: There is no module with the namespace "math".

2. require 批量引入

  • 问题: 想引入某个目录下的所有文件? 下面例子
// 批量引入.vue
const file = require.context('@/views/', true, /\.vue$/)
// 批量引入路由
const asyncFiles = require.context('./permissionModules', true, /\.ts$/)
let permissionModules: Array = []
asyncFiles.keys().forEach((key) => {
  if (key === './index.ts') return
  permissionModules = permissionModules.concat(asyncFiles(key).default)
})

如果提示找不到名称“require”。是否需要为节点安装类型定义? 请尝试使用 npm i --save-dev @types/node,然后将 “node” 添加到类型字段。

解决办法:
按照提示安装库
npm i --save-dev @types/node
然后在tsconfig.json types字段中添加node; 然后重启一下; 就行了

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": false,
    "jsx": "preserve",
    "moduleResolution": "node",
    "types": [
      "vite/client",
      "node",
    ],
     ...
    }
  }
}

3.别名配置

  • 问题: 当你使用别名@, 你会发现别名框架并没有配置 会出现提示 找不到模块“@/router/index”或其相应的类型声明
import router from '@/router/index';

解决办法:
vite.config.js 配置别名

// vite.config.js 
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
    // 配置别名
    alias: [
      { find: '@', replacement: resolve(__dirname, './src') },
      { find: 'views', replacement: resolve(__dirname, './src/views') },
    ],
  },
  css: {
    // 添加全局mixin.scss
    preprocessorOptions: {
      scss: {
        additionalData: '@import "@/style/libs.scss";',
      },
    },
  },
});

很多人以为这样就结束了;在js 文件中确实就已经可以用了; 但是在.vue 文件中还是不行,其实还需要在tsconfig.json里面配置对应的别名; 详情见https://www.jianshu.com/p/1477b68b2d69

// tsconfig.json 
{ 
  "compilerOptions": {
      ... 
      "paths": { 
        "@/*":[ "./src/*" ], 
        "views/*":[ "./src/views/*" ], 
      } 
    } 
}

4.pinia 使用

注释:pinia跟vuex一样也是一个基于vue的状态管理库;使用pinia时,涉及到扁平架构可以理解为它每一个store 都是动态的、独立的;删除了mutations,更好的支持typescript。搭配TS一起使用时有靠谱的类型推断支持。

pinia 安装:
npm install pinia
or
yarn add pinia

// src/store/index.ts 
import {createPinia} from "pinia"
const pinia = createPinia() 
export default pinia
//main.ts 
.... 
import pinia from "./store/index"
createApp(App).use(router).use(pinia).mount('#app')
定义store
// src/store/modules/user/index.ts 
import {defineStore} from "pinia" 
impor {UserInfoType} from "./type"
import {userInfo} from "@/server/user"
// 封装的路由
// defineStore的第一个参数是应用程序中 store 的唯一id;这些注解官网都有 
const userStore = defineStore('userStore',{
     // 类似于data
    state:()=>({ 
       user: {} as UserInfoType
     }),
     // 类似于computed
    getters:{ 
      getUser: state=>state.user, 
       ...
   }, 
   // 没有mutations!! 
   //actions类似于组件中methods, 可以定义一些业务逻辑; 或者复杂的$patch
   actions: { 
      async  getUserInfo () {
          const {data} = await userInfo()
          // this.user = data
          或者
         this.$patch(state=>{
             state.user = data
          })
      }
   }
}) 
export userStore
使用useStore
 // src/layout/index.vue 

 
状态更新的几种方式
// .vue


页面更新后提示报错信息:

问题: Uncaught Error: []: getActivePinia was called with no active Pinia. Did you forget to install pinia?
什么意思呢? 你当前正在使用pinia; 但是pinia 还没有挂载成功!!!! what? 我使用vuex就不会这样呀!!!! 戏精

解决方案:

无意间在廖雪峰的官网看到过一句评论!!所谓的对象、函数在浏览器内存中就是一个地址;找个地址只想某个函数或者对象或者方法、谁拿到这个地址就拥有操作的权利; 所谓的函数作为参数传入别的函数; 其实就是把地址给了它; 让它拥有的操作的权利! 我觉得挺精辟!!

使用时引入pinia

// src/store/index.ts 
import {createPinia} from "pinia"
const pinia = createPinia() 
export default pinia 
//_______________分割线__________________ // 

//src/store/asyncRoute.ts 
import routes from "@/mockjs/mock.routes"
// 路由元信息 
import useStore from "@/store/createRouteInfo"
// 封装的路由 
import pinia from "./index"
// 引入pinia 
const store = useStore(pinia) // 把pinia 传进去 就可以任意任意使用里面的属性了

6.Vue-router

6.1.动态路由遇到的问题合集 (后面附有完整动态路由案例)
  • 问题: Invalid route component at extractComponentsGuards ? 这个问题不陌生吧!! 有俩个因素;

1. 如果你用JSON.stringify()转为string 后存储; 因为JSON.stringify()在序列化的过程中function、undefined、symbol这几种类型是不可枚举属性; 序列化后这个键值对会消失; addRoute()的时候就会发现没有这个component.

2. 使用component:()=>import('views/Order/index.vue')懒加载方式引入;也会提示Invalid route component;

tips: 调试路由时可通过router.getRoutes(); 查看动态路由是否已经完整注入进去

解决办法1. 使用Glob Import 动态导入

// src/mockjs/mock.routes.ts
export default routes = [
    {
      path: '/',
      name: 'home',
      component: 'views/Home/index.vue',
      meta: {
        // 页面标题
        title: '首页',
        // 身份认证
        userAuth: false,
      },
    },
]
// src/router/index.ts
import routes from "@/mockjs/mock.routes"// 路由元信息

// 不推荐这种用法,这里只是为了踩坑,
const modules = import.meta.golb("../views/**/**.vue") //使用golb ++
routes.forEach(item=>{
    router.addRoute("home", {
          path: item.path,
          name: item.name,
          meta: ...item.meta,
          component:
             //本地能使用,上生产直接GG
            //()=>import(/* @vite-ignore */ `/@views/${itemRouter.component}`),
        //使用modules
            modules[/* @vite-ignore */ `../views/${item.component}`],
        })
})

解决办法2 : 在声明路由数据时使用 RouteRecordRaw; 下面是RouteRecordRaw的注解

当用户通过 routes option 或者 router.addRoute() 来添加路由时,可以得到路由记录。 有三种不同的路由记录:

  1. 单一视图记录:有一个 component 配置
  2. 多视图记录 (命名视图) :有一个 components 配置
  3. 重定向记录:没有 component 或 components 配置,因为重定向记录永远不会到达。
// src/mockjs/mock.routes.ts 
export default routes:RouteRecordRaw[] = [ 
  { 
    path: '/', 
    name: 'home', 
    component:  component: () => import("views/Home/index.vue"),
    meta: { 
        // 页面标题 
        title: '首页', 
        // 身份认证 
        userAuth: false, 
    }, 
  }, 
]

// src/router/index.ts 
import routes from "@/mockjs/mock.routes"
// 路由元信息  确保自己的地址是否正确
routes.forEach(item=>{ router.addRoute(item}) })

完整的动态路由权限

很多人做动态路由时 数据存在pinia; 页面刷新后页面空白或者进入404页面; 并且使用router.getRoutes() 查看新增的路由已经消失; 是因为store是存在浏览器的堆栈内存里面; 刷新后页面数据被释放掉; 那么如何解决呢? 想一想 页面刷新后我们的框架是会重新挂载的, 思考,是不是可以在路由权限里面访问actions addRoute()

公开路由和权限路由一般我会分开,方便维护

// src/router/permissions.ts
import { permissionRoutes } from "@/router"
import pinia, { userStore } from "@/store/index"
import { ElMessage } from 'element-plus';
//路由实例
import router from "./index"
import { RouteLocationNormalized } from "vue-router"
// 白名单
import { routeWhitelist } from "@/config/whitelist"
const store = userStore(pinia)
router.beforeEach(async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: any) => {
    // 防止token未获取到就执行或者取到一个失效的token
    let meta = to.meta, token = await store.getToken(); 
    //登录后
    if (!!token) {
        // 如果当前是登录页
        if (to.path === '/login') {
            next({ path: '/' })
        } else {
            if (store.$state.routeModules.length === 0) {
                try {
                    // 获取用户信息
                    const { result } = await store.getUsers()
                    // 当前地址需要登录 && 权限为admin
                    if (result?.roles === 'admin') {
                        //添加动态路由
                        store.$patch(state => {
                            state.routeModules = permissionRoutes
                        })
                        // 添加异步就是跟token一样的道理, 这是为什么会404或者白屏的原因
                        await store.addRouteModules()
                        next({...to, replace: true})
                    } else {
                        if (result?.roles === 'client') {
                            ElMessage.warning("普通用户暂不开放管理权限");
                            return
                        }
                        // 如果当前路由不是登录页,则执行跳转到登录页面
                        if (routeWhitelist.indexOf(to.path) !== -1 || to.name == 'NotFound') {
                            next()
                        } else {
                            next({ path: `/login?redirect=${to.path}`, replace: true })
                        }
                    }
                } catch (error) {
                    next(`/login?redirect=${to.path}`)
                }
            } else {
                next()
            }
        }
    } else {
        // 如果是前往登陆就正常跳转; 不是则强制到登陆页
        if (to.path == '/login') {
            next()
        } else {
            // 是否需要登录
            if ('userAuth' in meta && !meta.userAuth) {
                next()
            } else {
                next({ path: `/login?redirect=${to.path}`, replace: true })
            }
        }
    }

})

router.afterEach((to: RouteLocationNormalized) => {
    // title
    document.title = to.meta.title.toString()
})
// store/user/index.ts
import {RouteRecordRaw} from "vue-router"
import { loginType, UserInfoType } from './type';
import { toRaw } from '@vue/reactivity';
const userStore = defineStore('userStore', {
    state: () => ({
        routeModules: [] as RouteRecordRaw [] , // route
        userInfo: {} as UserInfoType, 
        token: '', // 单个数据ts会参与类型推断,除非你手欠中途强制改类型,
        ...
    }),
    getters: {
        ...
    },
    actions: {
        // token 获取
        async getToken() {
            return this.token = this.token ? this.token : localStorage.getItem(Storage.TOKEN) || null
        },
        // 用户信息获取
        async getUsers() {
            const res = await getUserProfile()
            if (res?.code === 200) {
                const { result } = res
                this.userInfo = result
                LocalStorage.set(Storage.USER, JSON.stringify(result))
            }
            return res
        },
        // 设置路由权限
        async addRouteModules() {
            toRaw(this.routeModules).forEach(el => {
                router.addRoute(el)
            })
            // console.log(router.getRoutes())
        }
    },
})

export default userStore
// main.js
import router from "./router/index"
 ..... 
import "./router/permissions"
 ..... 
createApp(App).use(router).use(pinia).mount('#app')
未完待续, 后续还会更新; 这些只是一部分; 欢迎大家参与留言讨论

你可能感兴趣的:(ts+vue3+vite+pinia+vue-router 踩坑合集(持续更新中...))