vue3+vite+ts+pinia+elplus那些不得不填的坑

一、关于element-plus

后台管理平台,当然是vue+element的组合最香。关于引入element-plus官网已经说的很清楚了,这里就不在赘述。配置成功之后,大部分组件我们都不需要手动进行引入了,系统会自动引入组件,我们直接使用就可以。

问题一:有几个组件,必须要单独引入才可以使用:

ElMessage/ElMessageBox/ElLoading需要在使用的组件里单独引入后再使用(ps: 本项目只涉及到这三个需要单独引入的组件,不排除还有其他组件),同时对应组件的样式也需要单独引入到main.ts里,这里我们可以通过配置解决样式需要手动引入问题。

// vite.config.ts

import { createStyleImportPlugin, ElementPlusResolve, } from 'vite-plugin-style-import'

export default defineConfig({
  plugins: [
    createStyleImportPlugin({
      resolves: [ElementPlusResolve()],
      libs: [
        {
          libraryName: 'element-plus',
          esModule: true,
          resolveStyle: (name: string) => {
             return `element-plus/theme-chalk/${name}.css`
          }
        }
      ]
    })
  ]
})

解决了上述组件和样式问题,我们又发现ElMessage在使用过程中,不会自动消失,同时会报一个错误 Cannot set properties of null(setting visible) 这个时候我们需要升级下vue的版本,升级到 3.2.32 以上的版本,就可以正常关闭消息提示了。

问题二:关于Icon的使用

关于Icon组件的使用,官网上也有说明,但是个人觉得说的不够详细,这里贴出本人使用方法,采取的是全局注册组件方式。

// main.ts
import * as Icons from '@element-plus/icons-vue'
// 定义icon组件
const Icon = (props: { icon: string }) => {
  const { icon } = props
  return createVNode(Icons[icon as keyof typeof Icons])
}

let app = createApp(App)
app.component('Icon', Icon)

// 组件中使用,使用时可以去官网查询需要的icon名字,同时需要设置样式,主要是尺寸
<Icon class="el-button-icon" :icon="'Refresh'" />

问题三:关于table组件的封装

在之前的文章 新系统开发问题记录 里有一部分内容是关于 element-ui table 的封装,升级到 element-plus 以后,封装方法大体一致,主要区别就是关于插槽的使用,因为vue3本身也对插槽做了修改。

// ==== 原封装关于插槽的使用
// table组件内部
<template slot-scope="scope">
  <template v-if="item.slot">
    <!-- 需要使用插槽展示的数据 -->
    <slot :name="item.prop" :scope="scope.row"></slot>
  </template>
  <template v-else>
    <!-- 直接获取属性展示内容 -->
    {{scope.row[item.prop]}}
  </template>
</template>
// 组件内使用
<template slot="status" slot-scope="data">
  <el-tag v-if="data.scope.status === 0" size="small">启用</el-tag>
  <el-tag v-else-if="data.scope.status === 1" size="small" type="danger">禁用</el-tag>
</template>

// ==== 新封装关于插槽的使用
// table组件内部
<template #default="scope">
  <template v-if="item.slot">
    <slot :name="item.prop" :row="scope.row"></slot>
  </template>
  <template v-else>
    {{ scope.row[item.prop] }}
  </template>
</template> 
// 组件内使用
<template #status="scope">
  <el-tag v-if="scope.row.status === 0" size="small">正常</el-tag>
  <el-tag v-else size="small" type="danger">禁用</el-tag>
</template>

问题四:关于element-plus组件类型别名

引入ts以后,在使用组件对应的方法的时候经常会出现经典的ts红也就是ts提示错误了,这时候我们可以引入element-plus组件对应的类型,这里简单说几种类型。

// ElTree
const elTreeRef = ref<InstanceType<type ElTree>>()
// 使用对应方法时
elTreeRef.value?.setCheckedKeys()
// Form
const formRef = ref<FormInstance>()
// table
const tableRef = ref<InstanceType<type ElTable>>()

二、 关于vue-router4

更新后的路由,有些方法和之前有所区别,甚至是有很大的出入,这里简单总结如下。

(一)、路由监听

在组件内部监听路由变化时,我们可以使用两种方式:

(1)、onBeforeRouterUpdate
onBeforeRouterUpdate(to => {})

(2)、watch监听,可以监听整个路由,也可以监听其中某个参数变化
watch(()=> router.currentRoute.value, (newval, oldVal)=>{
  console.log(newval, oldVal)
}, { immediate: true})

(二)、动态路由引入组件

在普通的路由配置中,我们通常会这么配置

{ path: '/index', component: () => import('@/pages/index.vue'), name: 'index'}

但是在此框架中,动态路由直接采用上述方式引入会提示报错The above dynamic import cannot be analyzed by vite,这其实不是router的问题,而是vite的一个警告,要消除警告有两种方式。

第一种:依旧使用import方式
{ path: item.path, component: () => defineAsyncComponent(()=>import(/* @vite-ignore */`@/pages/${item.name}.vue`)), name: item.name}

第二种:使用vite的glob导入
const modules = import.meta.glob('../pages/*.vue')
{ path: item.path, component: modules[`../pages/${menuItem.url}.vue`], name: item.name}

(三)、404路由配置

路由配置 404 跳转,传统方法中我们这样配置

{ path: '*', redirect: { name: '404' } }

但是在router4中,它又报错了Catch all routes ("*") must now be defined using a param with a custom regexp,我们需要这么修改。

{ path: '/:pathMatch(.*)', redirect: { name: '404' } }

三、关于变量的定义

(一)reactive定义数组

对于vue3的变量定义,我们已经知道可以使用reactive定义对象类型,ref定义基础类型。对于数组,使用两种方式均可,但是如果使用reactive定义数组,会发现我们直接对数组赋值或者用concat方法都无法获取到响应数组,此时我们可以采用以下几种方式进行修改。

// 方法一:对于已经获取的数组进行遍历,逐项push进变量里,简单示例:
let list = reactive([])
for(let i = 0; i < arr.length; i++){
    list.push(arr[i])
}

// 方法二:方法一太过麻烦,可以采用下面解构方法:
let list = reactive([])
list.push(...arr)

// 方法三:既然reactive可以用在对象上,那我们就嵌套一层对象在外层
let obj = reactive({
  list: []
})
obj.list = arr

// 方法四:我们可以使用ref去定义
let list = ref([])
list.value = arr

(二)关于props设置类型和默认值

vue3+ts接收props变量,我们可以直接使用defineProps进行定义

let props = defineProps({
  id: {
    type: number
  }
})

interface PropsType{
  id: number,
  text: string
}
let props1 = defineProps<PropsType>()

但是如果传递过来的props值,既有类型限制又需要设置初始默认值,此时我们就需要使用另一个方法withDefaults进行设置。

interface PropsType {
  id: number,
  text?: number,
  isAuth?: boolean
}
let props = withDefaults(defineProps<PropsType>(), {
  text: 'this is text',
  isAuth: false
})

(三)setup语法糖如何导出组件名字

在使用element-plus的menu组件的时候,因为动态菜单循环遍历,会存在调用组件自身的情况。如果在组件中直接引用自身,项目启动包括各种展示交互都是没问题的,但是编辑器里ts会报错,会提示找不到组件名字,这时候就需要导出组件的名字。

在2语法中,我们可以直接通过export default里的name属性进行导出,但是3里边没有这个导出了,结合网上的各种方法,总结如下方法解决。

方法一: 添加script标签导出,`此方法测试过,但是ts报错依旧存在,不知道是否是配置不对

<script lang='ts'>
export default {
    name: 'test'
}
</script>
<script lang='ts' setup>
</script>

方法二:使用unplugin-vue-define-options插件

npm i unplugin-vue-define-options -D

// vite.config.ts
import DefineOptions from 'unplugin-vue-define-options/vite'

export default defineConfig({
  plugins: [
    vue(),
    DefineOptions()
  ]
})

// tsconfig.json
{
  "compilerOptions": {
    "types": [
      "vite/client",
      "node",
      "unplugin-vue-define-options"
    ]
  }
}

// 组件内部
<script lang="ts" setup>
defineOptions({
  name: 'MainSlideSub'
});
</script>

这样配置之后组件内ts错误就消失啦~✿✿ヽ(°▽°)ノ✿

四、关于vite打包问题

凭实力开发的程序(bug)终于完成了,赶紧打包测试一下吧~果然不出意外地出现了各种error…warning…

问题一:打包时ts校验node_modules

众所周知,node_modules是肯定不接受任何校验和提交的,在这里需要对打包命令加一个–skipLibCheck即可:

// 原打包命令
"build": "vue-tsc --noEmit && vite build --mode production"
// 修改后
"build": "vue-tsc --noEmit --skipLibCheck && vite build --mode production",

问题二:调用自身组件提示props变量不存在

这个问题着实困扰了很久,整体程序运行不受影响,但是打包就有问题,而且只在问题三里我们使用的menu调用自身的组件里存在这个问题。说简单点,就是调用自身的组件传递props,在组件内使用的时候打包提示不存在这个props,属于ts提示的错误,最终采用下面方式解决。

// env.d.ts
declare let propsVal: any
declare let valFun: Function

问题三:”@charset” must be the first rule in the file

这个问题根本原因postcss会给含有中文的scss添加@charset=UTF-8,同时element-plus的index.scss文件里也有@charset=UTF-8,在打包合并的时候,两者就会合并,导致@charset=UTF-8不是出现在最前面而提示的警告⚠️。

解决方法可以禁止项目scss里添加@charset=UTF-8,同时配置删除库里的@charset=UTF-8

// vite.config.ts
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        charset: false
      }
    },
    postcss: {
      plugins: [
        {
          postcssPlugin: 'internal:charset-removal',
          AtRule: {
            charset: (atRule) => {
              if (atRule.name === 'charset') {
                atRule.remove();
              }
            }
          }
        }
      ]
    }
  },
})

以上四点就是本次所填的坑