这是我参与更文挑战的第3天,活动详情查看: 更文挑战javascript
从 Vue3
发布正式版以来,项目部分开始转用 Vue3+TS+Vite
的模式,Vite
的快
真的是让人不再可割舍,不再用由于改一行代码等待半天了,这TM真的是太幸福了,开发幸福指数直接干满。
(-^〇^-)css
下面记录一下最近一段时间以 Vue3+TS+Vite
模式开发踩的坑,在这里汇总一下,如你还遇到其余比较奇怪的问题地方,欢迎评论区评论交流。html
推荐一款 VSCode
插件,Volar
是一款针对 Vue
的打造的官方插件,在 第四届VueConf 中尤雨溪大大专门作了推荐。vue
用 VSCode
的铁汁们就有福了,虽然如今只有 21000
左右的下载量,但我以为之后确定会增长的。由于它是真的很是强大的,特别在对 Vue3
和一些 Vue
新的 RFC
都有很好的适配和及时的更新。java
安装方式很简单,直接在 vscode
的插件市场搜索 Volar
,而后点击安装就能够了,具体使用就慢慢本身去体会啦。node
对一块组件中要求的数据进行数据类型的规范是很是重要的,这样有利于组件的可维护,下面咱们尝试来规范比较常见的对象
、数组
、函数
这三种状况。webpack
在使用 Vue3+TS
对 props
进行复杂类型验证的时候,能够直接用 Vue
提供的 PropType
属性进行强制转换:ios
// 子组件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>
复制代码
也能以函数的形式来编写:git
// 子组件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>
复制代码
在 Vue2
的项目中,咱们能常常见到这样子的场景:github
// 挂载全局方法
Vue.prototype.$dateFormat = () => {
console.log('日期转换方法')
};
// 调用全局方法,在任何.vue文件中都能直接使用
this.$dateFormat();
复制代码
几乎全部 Vue2
项目都会把一些全局性的变量、方法直接挂载在 Vue
的原型上,这样子经过 this
调用,确实挺方便咱们使用的。
可是在:
Vue3
中是没有 this
!!!
Vue3
中是没有 this
!!!
Vue3
中是没有 this
!!!
(重要的事情说三遍)
虽然话是怎么说,可是 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 API
和 Composition 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() 形式。
使用 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()
期间才能调用这二者,更多妙用能够本身去细细尝试一下哦。 点我
在使用 vue-cli
构建的项目中,默认为咱们提供以 <%= htmlWebpackPlugin.options.title %>
的形式来动态为 HTML
插入内容。其过程是利用 webpack
提供的 HtmlWebpackPlugin
插件,在编译时,把模板变量替换为实际的 title
值。
而在 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文档 也有介绍别名的配置,咱们先按文档老老实实配置一下。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
})
复制代码
简简单单,是否是?(^ω^)
配置好了,咱们就直接在项目中使用,咱们假设引入一个CSS文件和一个TS文件:
呃...??? 报红了!可是引入的样式生效了,引入的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文件仍是报红的,这又是为何呢?
这里应该就已经不是 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
就没有报红了,大功告成,再也没有报错了。(-^〇^-)
安装
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>
复制代码
安装
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
,要不就一直报错。
我使用的
win7
系统,对于node-sass
依赖只能安装 5 如下的版本,主要缘由是node-sass
对node
版本有要求,要求node15
以上,可是node
从 14 左右就开始不支持win7
的安装了。
为开发服务器配置自定义代理也是一个老操做了,Vite
一样也提供了一个 server.proxy 来配置代理,其背后和 webpack
同样也是使用了 http-proxy 作为底层。
在使用 webpack
不少时候咱们可能都是以下作配置:
proxy: {
'/api': {
target: '代理的服务地址',
secure: true, // 配置https
changeOrigin: true, // 跨域, 本地会虚拟一个服务器接收并代转发你的请求
pathRewrite: { // 忽略前缀, 也就是不会加上/api这一层上去
'^/api': ''
}
}
}
复制代码
可是在使用 Vite
构建的项目如此配置却报错了!!!
查了一下 Vite文档 才知道是 pathRewrite
属性更名称了,如今它变成了 rewrite
且接收一个函数形式,正确的配置:
// vite.config.ts
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: '代理的服务地址',
secure: true,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
})
复制代码
在使用 vue-cli
的时候,咱们常常会使用到环境变量,用它来判断程序运行的环境:
const BASE_URL = process.env.NODE_ENV === 'production' ? 'http://production.com' : 'http://development.com';
复制代码
Vite
也提供了它本身的 环境变量 ,哎,xdm学习起来,学无止境。
Vite
经过 import.meta.env
来访问相关环境变量,咱们先把 import.meta
打印来看看:
能够看到它包含挺多东西的,但咱们重点看 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 = '橙某人-开发环境'
OTHER_TITLE = '其余名称-开发环境'
复制代码
// .env.production
VITE_TITLE = '橙某人-生产环境'
OTHER_TITLE = '其余名称-生产环境'
复制代码
文件内容如上,再打印 import.meta
查看,记得重启哦。
咱们能发现已经读取到这些额外的变量了,由于咱们执行的是 npm run dev
的命令启动项目,因此读取的会是 .env.development
文件的内容,若是执行 npm run build
则就会读取.env.production
文件。
并且 Vite
为了防止意外地将一些环境变量泄漏到客户端,只有将以 VITE_
为前缀的变量才会暴露给通过 Vite
处理的代码。
固然,咱们不只仅能建立 development
与 production
这两种模式的文件,若是你配置了 其余模式, 如 test
测试模式,你也能够建立 .env.test
文件来加载额外的变量。
.env # 全部状况下都会加载
.env.local # 全部状况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略
复制代码
原来在 vue-cli
的项目中,webpack
为咱们提供了 require.context() 方法,让咱们能够很方便的将一个文件夹下的全部文件一并导入。但该方法是 webpack
提供的,在 Vite
中天然不能用了,不过不用慌,vite
为咱们提供了 Glob 模式的模块导入,同样能实现文件的一并导入功能。
下面咱们来分别使用两者来实现将 @/components/
文件下的文件所有导入,并注册为全局组件,让咱们来看看二者有什么不一样。
// 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))
})
复制代码
// 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
大法打印点东西都能有黄线,哎,难受。(T_T)
解决:
// .eslintrc.js
module.exports = {
...
rules: {
// 开发环境不对console进行审查
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
复制代码
我的以为自增自减仍是挺好用的,看网上不少建议是能够写成 number.value += 1
这样子的形式,呃,看我的喜爱吧。
解决:
// .eslintrc.js
module.exports = {
...
rules: {
// 容许使用自增自减符号
'no-plusplus': [
'off',
{
allowForLoopAfterthoughts: true
}
]
}
}
复制代码
有时候为了方便想直接用系统的提示框,这也被提示黄线了。
解决:
// .eslintrc.js
module.exports = {
...
rules: {
// 去调用使用alert/confirm/prompt的warn
'no-alert': 0
}
}
复制代码
这是我在把上篇 完整的Axios封装-单独API管理层、参数序列化、取消重复请求、Loading、状态码... 文章改形成 TS
形式遇到的一个问题。
大体问题就是 eslint
默认不容许对函数参数再进行赋值操做,要不就会报红提示。
确实对函数参数中的变量进行赋值可能会误导读者,致使混乱,也会改变 arguments 对象。这确实是个有点危险性的操做,但有时候迫不得已,我就是想改(T_T),就如我在上面提到的文章中,我封装了一个取消重复请求的方法:
为了方便,我就是想在这个 addPending
方法中修改 cancelToken
属性。可是报红提示了,那有没有办法去掉这个提示呢?答案固然是有的,咱们须要配置一下 no-param-reassign 规则。
解决:
// .eslintrc.js
module.exports = {
...
rules: {
// 容许修改函数的入参
'no-param-reassign': [
'error',
{
props: true,
ignorePropertyModificationsFor: [
'config',
]
}
],
}
}
复制代码
配置后, config
就不报红啦,若是有更多其余参数名要配置,能够在 ignorePropertyModificationsFor
属性继续添加。
在我写这篇文章的时候,偶然在 ElementPlus 官网上发现了它居然新出了一块组件,这里就顺便写上来推荐推荐,感兴趣的小伙伴能够更新最新版的库下来玩一玩。
在咱们用 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
引入便可。
这是一个可能容易被忽略的特性,它大概的做用就是:可使用它来描述那些可以“经过索引获得”的类型,好比 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 }
复制代码
利用 可索引的类型
特性,咱们就能实现规范的类型属性随便增长了,不再用操心了。
呃,可是呢!!!这上面使用了 any
并且你有没有忽然以为整个接口类型都变得虚有图表,意义不是很大了?
哈哈哈,我告诉你.................这是错觉,不要在乎这些细节,咱们的写代码的第一目标就是代码能跑,也不是不能用就行,这是咱们的宗旨。
在
Vue3+TS+Vite2+ElementPlus+Eslint
项目实践中暂时有记忆的坑就先分享这些内容了,后面若是还有其余的,我会继续在此文再做补充。(=^▽^=)
至此,本篇文章就写完啦,撒花撒花。
但愿本文对你有所帮助,若有任何疑问,期待你的留言哦。 老样子,点赞+评论=你会了,收藏=你精通了。