Vue3+TS+Vite2+ElementPlus+Eslint項目實踐遇到的技巧/問題彙總

這是我參與更文挑戰的第3天,活動詳情查看: 更文挑戰javascript

前言

Vue3 發佈正式版以來,項目部分開始轉用 Vue3+TS+Vite 的模式,Vite真的是讓人不再可割捨,不再用由於改一行代碼等待半天了,這TM真的是太幸福了,開發幸福指數直接幹滿。
(-^〇^-)css

下面記錄一下最近一段時間以 Vue3+TS+Vite 模式開發踩的坑,在這裏彙總一下,如你還遇到其餘比較奇怪的問題地方,歡迎評論區評論交流。html

技巧/問題彙總

Volar插件

推薦一款 VSCode 插件,Volar 是一款針對 Vue 的打造的官方插件,在 第四屆VueConf 中尤雨溪大大專門作了推薦。vue

image.png

VSCode 的鐵汁們就有福了,雖然如今只有 21000左右的下載量,但我以爲之後確定會增長的。由於它是真的很是強大的,特別在對 Vue3 和一些 Vue 新的 RFC 都有很好的適配和及時的更新。java

image.png

安裝方式很簡單,直接在 vscode 的插件市場搜索 Volar,而後點擊安裝就能夠了,具體使用就慢慢本身去體會啦。node

Vue3中對props進行TS類型規範

對一塊組件中要求的數據進行數據類型的規範是很是重要的,這樣有利於組件的可維護,下面咱們嘗試來規範比較常見的對象數組函數這三種狀況。webpack

寫法一

在使用 Vue3+TSprops 進行復雜類型驗證的時候,能夠直接用 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>
複製代碼

Vue3中如何掛載全局方法

Vue2 的項目中,咱們能常常見到這樣子的場景:github

// 掛載全局方法
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')
    }
  }
})
複製代碼

簡簡單單,是否是?(^ω^)

配置好了,咱們就直接在項目中使用,咱們假設引入一個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 也提供了它本身的 環境變量 ,哎,xdm學習起來,學無止境。微信圖片_20201208153103.jpg

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 = '橙某人-開發環境'
OTHER_TITLE = '其餘名稱-開發環境'
複製代碼
// .env.production
VITE_TITLE = '橙某人-生產環境'
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常見的配置

Eslint 能夠說是一個磨人的小妖精,讓人又愛又恨。它一邊讓咱們心喜它帶來的代碼整潔統一,另外一邊又讓咱們到處受限它的魔爪之下。

微信圖片_20201229135933.png

但不少時候咱們仍是會選擇它,不爲別的,哎,就是玩兒。

去掉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 並且你有沒有忽然以爲整個接口類型都變得虛有圖表,意義不是很大了?

哈哈哈,我告訴你.................這是錯覺,不要在乎這些細節,咱們的寫代碼的第一目標就是代碼能跑,也不是不能用就行,這是咱們的宗旨。

Vue3+TS+Vite2+ElementPlus+Eslint 項目實踐中暫時有記憶的坑就先分享這些內容了,後面若是還有其餘的,我會繼續在此文再做補充。(=^▽^=)

至此,本篇文章就寫完啦,撒花撒花。

image.png

但願本文對你有所幫助,若有任何疑問,期待你的留言哦。 老樣子,點贊+評論=你會了,收藏=你精通了。

相關文章
相關標籤/搜索