Vue3+TS+Vite2+ElementPlus+Eslint项目实践遇到的技巧/问题汇总

技巧/问题汇总

Volar插件

推荐一款 VSCode 插件,Volar 是一款针对 Vue 的打造的官方插件,在 第四届VueConf 中尤雨溪大大专门做了推荐。

image.png

VSCode 的铁汁们就有福了,虽然现在只有 21000左右的下载量,但我觉得以后肯定会增加的。因为它是真的非常强大的,特别在对 Vue3 和一些 Vue 新的 RFC 都有很好的适配和及时的更新。

image.png

安装方式很简单,直接在 vscode 的插件市场搜索 Volar,然后点击安装就可以了,具体使用就慢慢自己去体会啦。

Vue3中对props进行TS类型规范

对一块组件中要求的数据进行数据类型的规范是非常重要的,这样有利于组件的可维护,下面我们尝试来规范比较常见的对象数组函数这三种情况。

写法一

在使用 Vue3+TSprops 进行复杂类型验证的时候,可以直接用 Vue 提供的 PropType 属性进行强制转换:

// 子组件Goods.vue
<script lang='ts'>
import {defineComponent, PropType} from 'vue'
interface IGoods {
  id: number;
  goodsName: string;
}
interface IFun {
  (): IGoods   
}
export default defineComponent({
  props: {
    // 对象
    goods: {
      required: true,
      type: Object as PropType<IGoods>
    },
    // 数组
    list: {
      required: true,
      type: Object as PropType<IGoods[]>
    },
    // 函数
    getInfo: {
      required: true,
      type: Function as PropType<IFun>
    }
  }
})
</script>

写法二

也能以函数的形式来编写:

// 子组件Goods.vue
<script lang='ts'>
import {defineComponent} from 'vue'
interface IGoods {
  id: number;
  goodsName: string;
}
interface IFun {
  (): IGoods   
}
export default defineComponent({
  props: {
    goods: {
      required: true,
      type: Object as () => IGoods
    },
    list: {
      required: true,
      type: Array as () => IGoods[]
    },
    getInfo: {
      required: true,
      type: Function as unknown as () => IFun
    }
  }
})
</script>

Vue3中如何挂载全局方法

在 Vue2 的项目中,我们能经常见到这样子的场景:

// 挂载全局方法
Vue.prototype.$dateFormat = () => {
    console.log('日期转换方法')
};
// 调用全局方法,在任何.vue文件中都能直接使用
this.$dateFormat();

几乎所有 Vue2 项目都会把一些全局性的变量、方法直接挂载在 Vue 的原型上,这样子通过 this 调用,确实挺方便我们使用的。
但是在:
Vue3 中是没有 this!!!
Vue3 中是没有 this!!!
Vue3 中是没有 this!!!
(重要的事情说三遍)

globalProperties

虽然话是怎么说,但是 Vue3 是兼容 Vue2 的,我们其实依旧也能使用 this ,但只能在原来的 Option API 中使用,不能在新的 Composition API 使用哦。而且 Vue3 也并不推荐在原型上做文章了,但尤雨溪大大为了让一些想要把 Vue2 升级为 Vue3 项目的用户过渡平滑,也推出了代替 Vue.prototype 原型的 globalProperties 方案。

如何挂载全局方法:

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)

app.config.globalProperties.$dateFormat = () => {
    console.log('日期转换方法')
};

app.mount('#app')

具体使用:

<script lang="ts">
import {defineComponent, onMounted, getCurrentInstance} from 'vue'
export default defineComponent({
  // Options API
  mounted() {
    this.$dateFormat()
  },
  // Composition API
  setup() {
    const { proxy } = getCurrentInstance()!;
    
    onMounted(() => {
      proxy.$dateFormat()
    })
    
    return {}
  }
})
</script>

上面分别展示 Option APIComposition API 获取全局方法的方式。

getCurrentInstance() 方法是用来访问内部组件实例,它身上挂载着很多方法,我们可以通过它的 proxy 属性访问到 globalProperties 身上进而访问到挂载的全局方法, 对于访问 globalProperties 我们也能换一种方式:

const { $dateFormat } = getCurrentInstance()!.appContext.config.globalProperties;
$dateFormat();

这里有个需要注意的地方,我看网上很多文章会以下面的形式去访问 globalProperties 身上挂载的东西:
const { ctx } = getCurrentInstance();
ctx.$dateFormat();
但这种方式只能在开发环境下使用,生产环境下的 ctx 将访问不到 globalProperties,也就是打包后访问 ctx.$dateFormat(); 是会报错。(Uncaught TypeError: ctx.$dateFormat is not a function
(但现在好像 Vue 改动了,开发环境也直接访问不了,YES,挺好!)

虽然上面通过 globalProperties 的方式挂载全局方法挺好用的,又能替代 Vue.prototype 也能在 Composition API 中使用,但看尤雨溪大大的意思好像是不建议这么用了,具体可以查看这个 vue/rfcs

虽然 Vue3 不推荐怎么挂载一些变量或方法,但是推荐使用依赖注入的 provide()和inject() 形式。

provide()/inject()

使用 project() 绑定依赖:

import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)

app.provide('$dateFormat', () => {
  console.log('日期转换方法')
})

app.mount('#app')

使用 inject() 获取:

<script lang="ts">
import {defineComponent, inject} from 'vue'
export default defineComponent({
  setup() {
    const $dateFormat: (() => void) | undefined = inject('$dateFormat')
    $dateFormat && $dateFormat()
    
    return {}
  }
})
</script>

provide/inject 的用法很简单,但需要注意只能在当前活动实例的 setup() 期间才能调用这两者,更多妙用可以自己去细细尝试一下哦。 点我

Vite中动态HTML内容的插件

在使用 vue-cli 构建的项目中,默认为我们提供以 <%= htmlWebpackPlugin.options.title %> 的形式来动态为 HTML 插入内容。其过程是利用 webpack 提供的 HtmlWebpackPlugin 插件,在编译时,把模板变量替换为实际的 title 值。

image.png

而在 Vite 中要实现这样的功能也非常简单,我们借助 vite-plugin-html 就能轻松实现。

安装:

npm install vite-plugin-html -D

配置:

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { injectHtml } from 'vite-plugin-html'

export default defineConfig({
  plugins: [
    vue(),
    injectHtml({
      injectData: {
        title: '用户管理系统'
      }
    })
  ],
})

具体使用,动态变量语法简单了许久

// index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= title %></title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

更多配置细节就好好看 vite-plugin-html文档 吧。(-^〇^-)

Vite中配置项目别名“@”的问题

配置项目别名现在也是一个项目必不可少的环节了,因为项目使用了 Vite 来构建,Vite文档 也有介绍别名的配置,我们先按文档老老实实配置一下。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
})
resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)) // 上面不起效果,使用此写法
    }
  }

简简单单,是不是?(^ω^)

配置好了,我们就直接在项目中使用,我们假设引入一个CSS文件和一个TS文件: 

image.png

呃...??? 报红了!但是引入的样式生效了,引入的TS文件中的方法也能正常调用。

因为项目用到了 eslint,怀疑这应该是 eslint 不识别项目别名报的红了,那我们修改下它的配置应该就可以啦! 解决 eslint 不识别项目别名的方式有很多种,这里我们先直接选择最暴力的,关闭它相关的 ESLint rules:

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: ['plugin:vue/essential', 'airbnb-base', 'plugin:prettier/recommended'],
  parserOptions: {
    ecmaVersion: 12,
    parser: '@typescript-eslint/parser',
    sourceType: 'module'
  },
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    'import/no-unresolved': 'off', // 关闭对别名(@)审查
    'import/no-extraneous-dependencies': 'off' // 关闭内置模块审查
  }
}

修改完 eslint 的配置后,我们能发现引入的CSS文件已经不报红了,但TS文件还是报红的,这又是为什么呢?

image.png

这里应该就已经不是 eslint 的问题了,可能是 VSCode 编辑器审查语法的问题了。VSCode 语法检查、tsc编译都需要依赖 tsconfig.json 文件中的配置,那我们就再配置一下 TS 中的别名看看。

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "paths": {
      "@/*": ["./src/*"], // 配置ts别名
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

修改完 tsconfig.json 就没有报红了,大功告成,再也没有报错了。(-^〇^-)

Vite中全局引入CSS变量

Less变量

安装

npm install less less-loader -D

配置

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      less: {
        modifyVars: {
          hack: `true; @import (reference) "${resolve('src/assets/lessVar.less')}";`
        },
        javascriptEnabled: true
      }
    }
  }
})

具体使用

// lessVar.less
@primary: #43AFFF;
// 在任意.vue文件中可以直接使用变量
<style lang="less" scoped>
h1 {
   color: @primary;
}
</style>

Scss变量

安装

npm install sass sass-loader node-sass@4.14.1 -D

配置

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: '@import "src/assets/scssVar.scss";'
      }
    }
  }
})

具体使用

// scssVar.less
$primary: #43AFFF;
// 在任意.vue文件中可以直接使用变量
<style lang="scss" scoped>
h1 {
   color: $primary;
}
</style>

在使用 scss 踩了两个坑: 

Vite 中使用 scss 强制要求下载 sass ,要不就一直报错。 image.png

我使用的 win7 系统,对于 node-sass 依赖只能安装 5 以下的版本,主要原因是 node-sassnode 版本有要求,要求 node15 以上,但是 node 从 14 左右就开始不支持win7的安装了。 image.png

Vite中代理功能rewrite属性的改变

为开发服务器配置自定义代理也是一个老操作了,Vite 同样也提供了一个 server.proxy 来配置代理,其背后和 webpack 一样也是使用了 http-proxy 做为底层。

在使用 webpack 很多时候我们可能都是如下做配置:

proxy: {
  '/api': {
    target: '代理的服务地址',
    secure: true, // 配置https
    changeOrigin: true, // 跨域, 本地会虚拟一个服务器接收并代转发你的请求
    pathRewrite: { // 忽略前缀, 也就是不会加上/api这一层上去
      '^/api': ''
    }
  }
}

但是在使用 Vite 构建的项目如此配置却报错了!!!

image.png

查了一下 Vite文档 才知道是 pathRewrite 属性改名称了,现在它变成了 rewrite 且接收一个函数形式,正确的配置:

// vite.config.ts
export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: '代理的服务地址',
        secure: true,
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
})

Vite中的环境变量

在使用 vue-cli 的时候,我们经常会使用到环境变量,用它来判断程序运行的环境:

const BASE_URL = process.env.NODE_ENV === 'production' ? 'http://production.com' : 'http://development.com';

Vite 也提供了它自己的 环境变量

Vite 通过 import.meta.env 来访问相关环境变量,我们先把 import.meta 打印来看看: 

image.png

可以看到它包含挺多东西的,但我们重点看 env 属性,它身上内置了一些环境变量:

  • import.meta.env.BASE_URL:string类型,部署应用时的基本URL,他由 base配置项 决定。
  • import.meta.env.DEV:boolean类型,应用是否运行在开发环境(永远与 import.meta.env.PROD 相反)。
  • import.meta.env.MODE:string类型,应用运行的模式,它由 mode配置项 决定。
  • import.meta.env.PROD:boolean类型,应用是否运行在生产环境。
  • import.meta.env.SSR:boolean类型,是否是SSR应用,更多详情

注意在生产环境中,这些环境变量会在构建时被静态替换,因此,在引用它们时请使用完全静态的字符串。动态的 key 将无法生效。例如,动态 key 取值 import.meta.env[key] 是无效的。

如何创建额外的环境变量

我们能通过创建不同模式下的配置文件来加载额外的变量,比如,我们在项目根目录下创建 .env.development.env.production 文件。

// .env.development
VITE_TITLE = 'XXX-开发环境'
OTHER_TITLE = '其他名称-开发环境'

文件内容如上,再打印 import.meta 查看,记得重启哦。

 

image.png

我们能发现已经读取到这些额外的变量了,因为我们执行的是 npm run dev 的命令启动项目,所以读取的会是 .env.development 文件的内容,如果执行 npm run build 则就会读取.env.production 文件。

而且 Vite 为了防止意外地将一些环境变量泄漏到客户端,只有将以 VITE_ 为前缀的变量才会暴露给经过 Vite 处理的代码。

当然,我们不仅仅能创建 developmentproduction 这两种模式的文件,如果你配置了 其他模式, 如 test 测试模式,你也可以创建 .env.test 文件来加载额外的变量。

.env                # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略

Vite中用globEager替代require.context

原来在 vue-cli 的项目中,webpack 为我们提供了 require.context() 方法,让我们可以很方便的将一个文件夹下的所有文件一并导入。但该方法是 webpack 提供的,在 Vite 中自然不能用了,不过不用慌,vite 为我们提供了 Glob 模式的模块导入,一样能实现文件的一并导入功能。

下面我们来分别使用二者来实现将 @/components/ 文件下的文件全部导入,并注册为全局组件,让我们来看看两者有什么不同。

require.context

// main.js
const allCom = require.context('@/components/', true, /\.vue/);

allCom.keys().forEach(key => {
    const fullName = key.substr(key.lastIndexOf('/') + 1)
    const comName = fullName.split('.')[0].toUpperCase()
    Vue.component(comName, allCom(key).default || allCom(key))
})

globEager

// main.ts
const allCom = import.meta.globEager('./components/*.vue')

Object.keys(allCom).forEach((key) => {
  const files = key.substr(key.lastIndexOf('/') + 1)
  const name = files.split('.')[0].toUpperCase()
  app.component(name, allCom[key].default)
})

该 Glob 模式会被当成导入标识符:必须是相对路径(以 ./ 开头)或绝对路径(以 / 开头,相对于项目根目录解析),无法使用项目别名

导入全部文件的常见场景,除了动态注册全局组件外,还有一种就是注册路由的场景了,但注册路由的情况就不推荐使用 import.meta.globEager了,推荐使用 import.meta.glob,因为它默认具有懒加载性质,并会在构建时分离为独立的 chunk

Eslint常见的配置

去掉console的warn提示

用一下 console 大法打印点东西都能有黄线,哎,难受。(T_T)

image.png

解决:

// .eslintrc.js
module.exports = {
  ...
  rules: {
    // 开发环境不对console进行审查
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 
  }
}

允许使用自增('++')自减('--')符号

个人觉得自增自减还是挺好用的,看网上很多建议是可以写成 number.value += 1 这样子的形式,呃,看个人喜好吧。

image.png

解决:

// .eslintrc.js
module.exports = {
  ...
  rules: {
    // 允许使用自增自减符号
    'no-plusplus': [
      'off',
      {
        allowForLoopAfterthoughts: true
      }
    ]
  }
}

去掉alert()的warn提示

有时候为了方便想直接用系统的提示框,这也被提示黄线了。

image.png

解决:

// .eslintrc.js
module.exports = {
  ...
  rules: {
    // 去调用使用alert/confirm/prompt的warn
    'no-alert': 0 
  }
}

允许对函数参数再赋值

这是我在把上篇 完整的Axios封装-单独API管理层、参数序列化、取消重复请求、Loading、状态码... 文章改造成 TS 形式遇到的一个问题。

大致问题就是 eslint 默认不允许对函数参数再进行赋值操作,要不就会报红提示。

image.png

确实对函数参数中的变量进行赋值可能会误导读者,导致混乱,也会改变 arguments 对象。这确实是个有点危险性的操作,但有时候无可奈何,我就是想改(T_T),就如我在上面提到的文章中,我封装了一个取消重复请求的方法:

image.png

为了方便,我就是想在这个 addPending 方法中修改 cancelToken 属性。但是报红提示了,那有没有办法去掉这个提示呢?答案当然是有的,我们需要配置一下 no-param-reassign 规则。

解决:

// .eslintrc.js
module.exports = {
  ...
  rules: {
    // 允许修改函数的入参
    'no-param-reassign': [
      'error',
      {
        props: true,
        ignorePropertyModificationsFor: [
          'config',
        ]
      }
    ],
  }
}

配置后, config 就不报红啦,如果有更多其他参数名要配置,可以在 ignorePropertyModificationsFor 属性继续添加。

ElementPlus中的新组件 - SelectV2 - 虚拟列表选择器

在我写这篇文章的时候,偶然在 ElementPlus 官网上发现了它竟然新出了一块组件,这里就顺便写上来推荐推荐,感兴趣的小伙伴可以更新最新版的库下来玩一玩。

image.png

ElementPlus中使用Loading组件的TS类型

在我们用 TS 二次封装 ElementPlus 的组件的时候,直接选择用它的 type 类型更方便一点哦。

<template>
  <div>
    <button @click="clickEvent">点击</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { ElLoading } from 'element-plus'
import { ILoadingOptions } from 'element-plus/lib/el-loading/src/loading.type'
import 'element-plus/lib/theme-chalk/index.css'

export default defineComponent({
  setup() {
    // 封装loading方法
    function openLoading(loadingOptions?: ILoadingOptions) {
      ElLoading.service(loadingOptions)
    }

    function clickEvent() {
      openLoading({ fullscreen: true })
    }

    return {
      clickEvent
    }
  }
})
</script>

其他组件的类型规范也对应在源码中找到相关的 type 引入即可。

TS中的可索引的类型

这是一个可能容易被忽略的特性,它大概的作用就是:可以使用它来描述那些能够“通过索引得到”的类型,比如 a[10]ageMap["daniel"]文档

之所以讲到这个特性,主要是在项目中看到其他成员写了一些这样子的代码:

// 大致模样
interface Goods {
  goodsName: string
}
let goods: Goods = {
  goodsName: '一号商品'
}
goods = { goodsName: '二号商品', aliasGoodsName: '商品别名' } as Goods;

上面代码通过 as 断言语句,绕开编辑器检查,让商品对象增加了一个 aliasGoodsName 属性,但是 Goods 接口确没有相关描述,这有时会让其他项目成员很捉摸不透,因为商品对象是用 Goods 接口规范的,所有属性应该都很明确才对,并不存在什么不确定性。

下面我们先介绍一下,比较正确的方式来解决这种情况,直接使用 可选属性

interface Goods { // 接口定义类型
  goodsName: string
  aliasGoodsName?: string
}
let goods: Goods = {
  goodsName: '一号商品'
}
goods = { goodsName: '二号商品', aliasGoodsName: '商品别名' };

可选属性 能够很好的应对这种情况,但是增加个别属性还好,如果所规范的对象存在的不确定性非常大,增加的是十个?二十个呢(极端情况)? 如果我们还使用这种方式,就每次都要改 Goods 接口,这就变麻烦了。

懒是激发潜力的重要动力,这有没有一种一劳永逸的方式呢?答案当然是用的,且看:

interface Goods {
  goodsName: string
  [proName: string]: any
}
let goods: Goods = {
  goodsName: '一号商品'
}
goods = { goodsName: '二号商品', aliasGoodsName: '商品别名' }
goods = { goodsName: '三号商品', price: 100 }

利用 可索引的类型 特性,我们就能实现规范的类型属性随便增加了,再也不用操心了。

微信图片_20201229101857.png

呃,但是呢!!!这上面使用了 any 而且你有没有突然觉得整个接口类型都变得虚有图表,意义不是很大了?

哈哈哈,我告诉你.................这是错觉,不要在意这些细节,我们的写代码的第一目标就是代码能跑,也不是不能用就行,这是我们的宗旨。


posted @ 2023-01-04 16:01  爵岚  阅读(650)  评论(0编辑  收藏  举报