Vue項(xiàng)目中引入ESLint校驗(yàn)代碼避免代碼錯(cuò)誤
1 ESLint 是什么
ESLint 是一個(gè)插件式的 JavaScript / JSX 代碼檢查工具,用于檢測(cè)和修復(fù) JavaScript 代碼中的問(wèn)題,目標(biāo)是讓代碼更一致并避免錯(cuò)誤。
2 在 Vue 項(xiàng)目中引入 ESLint
使用 Vue CLI 搭建的 Vue2 項(xiàng)目已經(jīng)自帶 ESLint,就不贅述,我們看下 Vite 搭建的 Vue3 項(xiàng)目中怎么引入 ESLint。
使用以下命令搭建一個(gè) Vue3 項(xiàng)目:
npm create vite@latest vue3-project
創(chuàng)建之后,啟動(dòng)起來(lái):
npm i npm run dev
效果如下:

2.1 引入 ESLint
執(zhí)行以下命令:
npm init @eslint/config
進(jìn)入交互式界面,可通過(guò)上下方向鍵選擇,通過(guò)按回車(chē)鍵確定。
第一個(gè)問(wèn)題是:
- 你希望用 ESLint 來(lái)干嘛?
- 我們選擇最全面的那個(gè):檢查語(yǔ)法,發(fā)現(xiàn)問(wèn)題,并強(qiáng)制統(tǒng)一代碼樣式
$ npm init @eslint/config ? How would you like to use ESLint? … To check syntax only To check syntax and find problems ? To check syntax, find problems, and enforce code style
第二個(gè)問(wèn)題是:
- 你的項(xiàng)目用的是什么模塊系統(tǒng)?
- 因?yàn)槭沁\(yùn)行在瀏覽器端,選擇
ESModule
? What type of modules does your project use? … ? JavaScript modules (import/export) CommonJS (require/exports) None of these
第三個(gè)問(wèn)題是:
- 你用的什么框架?(居然沒(méi)有 Angular)
- 選擇
Vue
? Which framework does your project use? … React ? Vue.js None of these
第四個(gè)問(wèn)題是:
- 你是否使用 TypeScript?
- 選擇
Yes
? Does your project use TypeScript? ? No / Yes
第五個(gè)問(wèn)題是:
- 你的代碼運(yùn)行在什么環(huán)境?(這個(gè)可以多選)
- 選擇
Browser瀏覽器環(huán)境
? Where does your code run? … (Press <space> to select, <a> to toggle all, <i> to invert selection) ? Browser ? Node
第六個(gè)問(wèn)題是:
- 你想定義怎樣的代碼風(fēng)格?
- 選擇使用一個(gè)流行的代碼風(fēng)格
? How would you like to define a style for your project? … ? Use a popular style guide Answer questions about your style
第七個(gè)問(wèn)題是:
- 你想使用哪個(gè)樣式風(fēng)格?
Airbnb用的人比較多,就選這個(gè)吧
? Which style guide do you want to follow? … ? Airbnb: https://github.com/airbnb/javascript Standard: https://github.com/standard/standard Google: https://github.com/google/eslint-config-google XO: https://github.com/xojs/eslint-config-xo
第八個(gè)問(wèn)題是:
- 配置文件用什么格式?
- 就選 JavaScript 吧(生成
eslintrc.js文件)
? What format do you want your config file to be in? … ? JavaScript YAML JSON
完成!是不是超級(jí)簡(jiǎn)單!
看下我們都選了哪些配置:
? How would you like to use ESLint? · style ? What type of modules does your project use? · esm ? Which framework does your project use? · vue ? Does your project use TypeScript? · Yes ? Where does your code run? · browser ? How would you like to define a style for your project? · guide ? Which style guide do you want to follow? · airbnb ? What format do you want your config file to be in? · JavaScript
主要給我們安裝了以下依賴(lài):
eslint-config-airbnb-base@15.0.0
eslint-plugin-import@2.26.0
eslint-plugin-vue@9.2.0
eslint@8.20.0
@typescript-eslint/parser@5.30.6
@typescript-eslint/eslint-plugin@5.30.6
并生成了一個(gè) eslintrc.cjs 配置文件:
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'plugin:vue/vue3-essential',
'airbnb-base',
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: [
'vue',
'@typescript-eslint',
],
// 自定義 rules 規(guī)則
rules: {
},
};
2.2 ESLint 配置
- parser 解析器
- extends 配置擴(kuò)展
- plugins 插件
- rules 自定義規(guī)則 eslint.org/docs/latest…
- eslint-disable-next-line 禁用ESLint
2.3 執(zhí)行 ESLint 代碼檢查
在 package.json 文件的 scripts 中配置 lint 腳本命令:
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
// 配置 lint 腳本命令
"lint": "eslint --ext .vue,.ts src/"
},
執(zhí)行 lint 腳本命令:
npm run lint
出現(xiàn)了一堆報(bào)錯(cuò):
/vue3-project/src/App.vue 4:53 error Missing semicolon semi /vue3-project/src/components/HelloWorld.vue 2:26 error Missing semicolon semi 4:31 error Missing semicolon semi 6:21 error Missing semicolon semi /vue3-project/src/main.ts 1:32 error Missing semicolon semi 2:21 error Missing semicolon semi 3:28 error Missing semicolon semi 5:29 error Missing semicolon semi /vue3-project/src/vite-env.d.ts 4:3 error Expected 1 empty line after import statement not followed by another import import/newline-after-import 4:45 error Missing semicolon semi 5:48 error Missing semicolon semi 6:27 error Missing semicolon semi ? 12 problems (12 errors, 0 warnings) 12 errors and 0 warnings potentially fixable with the `--fix` option.
大部分都是說(shuō)句尾沒(méi)有分號(hào),因?yàn)槲覀冞x擇的是 Airbnb 代碼規(guī)范,所以會(huì)有這個(gè)報(bào)錯(cuò)提示,不同的代碼規(guī)范,內(nèi)置的檢查規(guī)則不一定完全相同。
2.4 自動(dòng)修復(fù) ESLint 問(wèn)題
在 scripts 中增加自動(dòng)修復(fù) ESLint 問(wèn)題的腳本命令:
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"lint": "eslint --ext .vue,.ts src/",
// 自動(dòng)修復(fù) ESLint 問(wèn)題腳本命令
"lint:fix": "eslint --ext .vue,.ts src/ --fix"
},
執(zhí)行:
npm run lint:fix
執(zhí)行自動(dòng)修復(fù)的命令之后,所有分號(hào)都加上了,未使用的變量也自動(dòng)移除了。
再次執(zhí)行:
npm run lint
沒(méi)有再報(bào)錯(cuò)。
3 配置 husky 和 PR 門(mén)禁
3.1 配置 husky 門(mén)禁
為了確保每次提交(git commit)之前代碼都通過(guò) ESLint 檢查,我們?cè)黾右粋€(gè) pre-commit 門(mén)禁。
- 第一步:安裝 husky 和 lint-staged
npm i lint-staged husky -D
- 第二步:在 package.json 的 scripts 中增加 prepare 腳本命令
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"lint": "eslint --ext .vue,.ts src/",
"lint:fix": "eslint --ext .vue,.ts src/ --fix",
// 在 npm install 之后自動(dòng)執(zhí)行,生成`.husky`目錄。
"prepare": "husky install"
},
- 第三步:執(zhí)行 prepare 腳本
npm run prepare
該命令執(zhí)行完會(huì)在項(xiàng)目根目錄自動(dòng)生成.husky目錄。
- 第四步:增加 pre-commit 鉤子
執(zhí)行以下命令,會(huì)在.husky目錄自動(dòng)生成pre-commit文件鉤子。
npx husky add .husky/pre-commit "npx lint-staged"
- 第五步:增加 lint-staged 配置
"lint-staged": {
"src/**/*.{vue,ts}": "eslint --fix"
},
通過(guò)以上五個(gè)步驟,以后每次使用git commit命令提交提交代碼,都會(huì):
- 被 pre-commit 鉤子攔截
- 執(zhí)行 npx lint-staged 命令
- 進(jìn)而執(zhí)行 eslint --fix 命令,對(duì)本次提交修改的代碼涉及的文件進(jìn)行代碼檢查,并自動(dòng)修復(fù)能修復(fù)的錯(cuò)誤,不能修復(fù)的錯(cuò)誤會(huì)提示出來(lái),只有所有 ESLint 錯(cuò)誤都修復(fù)了才能提交成功
3.2 配置 PR 門(mén)禁
如果你在做自己的開(kāi)源項(xiàng)目,并且非常幸運(yùn),有一群志同道合的小伙伴愿意一起參與貢獻(xiàn),這時(shí)為了統(tǒng)一大家的代碼風(fēng)格,讓貢獻(xiàn)者們專(zhuān)注于特性開(kāi)發(fā),不用擔(dān)心代碼格式規(guī)范問(wèn)題,并通過(guò) ESLint 工具提示貢獻(xiàn)者,哪些代碼可能帶來(lái)潛在的風(fēng)險(xiǎn),你就有必要給提交的 PR 加上 ESLint 門(mén)禁。
我們已經(jīng)增加了本地的 ESLint 命令:
"scripts": {
"lint": "eslint --ext .vue,.ts src/",
},
我們需要在本目錄創(chuàng)建一個(gè).github/workflows/pull-request.yml文件,在該文件中寫(xiě)入以下內(nèi)容:
name: Pull Request
on:
push:
branches: [ dev, main ]
pull_request:
branches: [ dev, main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
name: "ESLint"
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install pnpm
uses: npm/action-setup@v2
with:
version: 6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install deps
run: npm i
- name: ESLint
run: npm run lint
這樣只要 PR 是往 dev 或 main 分支合入的,都會(huì)跑一遍這個(gè) Github Actions 工作流任務(wù),ESLint 檢查不通過(guò)的話(huà),PR 的 checks 里面會(huì)提示,攔截該 PR 的合入。
PR 的提交者看到提示,也可以點(diǎn)到任務(wù)里面去看是哪里報(bào)錯(cuò),修改掉這些 ESLint 問(wèn)題,PR 就會(huì)變成綠色,項(xiàng)目的管理員就可以順利合入 PR 到目標(biāo)分支啦??
4 常見(jiàn)的 ESLint 問(wèn)題及修復(fù)案例
接下來(lái)跟大家分享 Vue DevUI 開(kāi)源 Vue3 組件庫(kù) ESLint 問(wèn)題修復(fù)過(guò)程中遇到的典型問(wèn)題。
4.1 案例1:
warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
該問(wèn)題出現(xiàn)頻率比較高,原因是有些類(lèi)型寫(xiě)了any,需要明確的類(lèi)型。
比如Pagination組件的單元測(cè)試文件pagination.spec.ts中:
const wrapper = mount({
components: {
DPagination
},
template: `<d-pagination ... />`
}, globalOption);
const btns = wrapper.findAll('a.devui-pagination-link');
expect(btns.map((ele: any) => ele.text()).join()).toEqual('<,1,...,4,5,6,...,16,>');
其中的ele: any就屬于這類(lèi)問(wèn)題。
解決辦法是給ele加上明確的類(lèi)型,看邏輯是<button>元素,由于是@vue/test-utils庫(kù)的包裹元素,因此需要包一層DOMWrapper:
import { DOMWrapper } from '@vue/test-utils';
expect(btns.map((ele: DOMWrapper<Element>) => ele.text()).join()).toEqual('<,1,...,4,5,6,...,16,>');
4.2 案例2:
'xxx' was used before it was defined no-use-before-define
這也是一個(gè)比較常見(jiàn)的問(wèn)題,在聲明之前使用變量或方法,解決辦法也很簡(jiǎn)單,只需要調(diào)整下代碼的順序即可,將變量或方法的聲明放在調(diào)用的語(yǔ)句之前。
比如Pagination組件的pagination.tsx中:
// 極簡(jiǎn)模式下,可選的下拉選擇頁(yè)碼
const litePageOptions = computed(() => liteSelectOptions(totalPages.value));
// 當(dāng)前頁(yè)碼
const cursor = computed({
get() {
// 是否需要修正錯(cuò)誤的pageIndex
if (!props.showTruePageIndex && props.pageIndex > totalPages.value) {
emit('update:pageIndex', totalPages.value || 1);
return totalPages.value || 1;
}
return props.pageIndex || 1;
},
set(val: number) {
emit('update:pageIndex', val);
}
});
// 總頁(yè)數(shù)
const totalPages = computed(() => Math.ceil(props.total / props.pageSize));
其中的totalPages的聲明在比較靠后的位置,但是卻在聲明之前在litePageOptions和cursor變量中都使用了totalPages,所以提示 ESLint 問(wèn)題。
解決的方法就是將totalPages的聲明放在litePageOptions和cursor之前。
// 總頁(yè)數(shù)
const totalPages = computed(() => Math.ceil(props.total / props.pageSize));
// 極簡(jiǎn)模式下,可選的下拉選擇頁(yè)碼
const litePageOptions = computed(() => liteSelectOptions(totalPages.value));
// 當(dāng)前頁(yè)碼
const cursor = computed({ ... });
4.3 案例3:
warning Missing return type on function @typescript-eslint/explicit-module-boundary-types
該問(wèn)題是因?yàn)楹瘮?shù)缺少返回類(lèi)型,比如Fullscreen組件utils.ts文件的launchImmersiveFullScreen方法中:
export const launchImmersiveFullScreen = async (docElement: any) => {
let fullscreenLaunch = null;
if (docElement.requestFullscreen) {
fullscreenLaunch = docElement.requestFullscreen();
} else if (docElement.mozRequestFullScreen) {
fullscreenLaunch = docElement.mozRequestFullScreen();
} else if (docElement.webkitRequestFullScreen) {
fullscreenLaunch = Promise.resolve(docElement.webkitRequestFullScreen());
} else if (docElement.msRequestFullscreen) {
fullscreenLaunch = Promise.resolve(docElement.msRequestFullscreen());
}
return await fullscreenLaunch.then(() => !!document.fullscreenElement);
};
先看下launchImmersiveFullScreen方法的參數(shù)問(wèn)題,docElement用了any,也缺失了返回類(lèi)型,docElement其實(shí)就是document對(duì)象,可以使用HTMLElement類(lèi)型,但是launchImmersiveFullScreen這個(gè)方法是用來(lái)啟動(dòng)沉浸式全屏的,為了實(shí)現(xiàn)瀏覽器兼容,比如使用了docElement.mozRequestFullScreen兼容火狐,而這些方法在HTMLElement中是沒(méi)有的,會(huì)報(bào)TS類(lèi)型錯(cuò)誤,所以需要做一些改造。
interface CompatibleHTMLElement extends HTMLElement {
mozRequestFullScreen?: () => void;
webkitRequestFullScreen?: () => void;
msRequestFullscreen?: () => void;
}
這里定義了一個(gè)CompatibleHTMLElement的類(lèi)型,繼承了HTMLElement,并增加了一些自定義的方法。
export const launchImmersiveFullScreen = async (docElement: CompatibleHTMLElement) => {
...
}
再來(lái)看下launchImmersiveFullScreen方法的返回類(lèi)型問(wèn)題。
return await fullscreenLaunch.then(() => !!document.fullscreenElement);
該方法返回了一個(gè)Promise對(duì)象,它的類(lèi)型是一個(gè)泛型,我們需要傳入具體的類(lèi)型:
export const launchImmersiveFullScreen = async (docElement: CompatibleHTMLElement): Promise<boolean> => {
...
return await fullscreenLaunch.then(() => !!document.fullscreenElement);
};
4.4 案例4:
'xxx' is already declared in the upper scope @typescript-eslint/no-shadow
這個(gè)問(wèn)題是由于嵌套的作用域中定義了相同的變量名,比如Tree組件的use-checked.ts文件中:
export default function useChecked(...) {
const onNodeClick = (item: TreeItem) => {
// 這里定義了 id 變量
const { id } = item;
...
filter 里面又定義了一個(gè) id 參數(shù)
const currentSelectedItem = flatData.filter(({ id }) => currentSelected[id] && currentSelected[id] !== 'none');
...
}
}
修改方式就是將其中一個(gè) id 的名字改了,比如把里面的 id 改成 itemId:
const currentSelectedItem = flatData.filter(({ id: itemId }) => currentSelected[itemId] && currentSelected[itemId] !== 'none');
歡迎在評(píng)論區(qū)分享你在項(xiàng)目中遇到的 ESLint 問(wèn)題????
5 感謝
在 Vue DevUI 組件庫(kù) ESLint 問(wèn)題清零之路上,不得不提的一位小伙伴就是 @linxiang07 同學(xué),他從2022年3月到7月,持續(xù)4個(gè)多月,累計(jì)修復(fù)40多個(gè)組件的100多個(gè) ESLint 問(wèn)題,直到7月5日 ESLint 清零,并給 PR 添加 ESLint 門(mén)禁,后續(xù) PR 如果未通過(guò) ESLint 檢查將無(wú)法合入。
感謝 linxiang 同學(xué)的付出!

以下是 linxiang 同學(xué)提交的部分PR:

linxiang 同學(xué)也因此成為 Vue DevUI 組件庫(kù) TOP3 的貢獻(xiàn)者,并成為我們的 Committer 和管理員????

值得一提的是 linxiang 同學(xué)還是我們的 VirtualList 虛擬列表組件和 Tree 組件等多個(gè)組件的田主和貢獻(xiàn)者,并且完善了多個(gè)組件的單元測(cè)試,能力很強(qiáng),是非?;钴S和積極的貢獻(xiàn)者。
以上就是Vue項(xiàng)目中引入ESLint校驗(yàn)代碼避免代碼錯(cuò)誤的詳細(xì)內(nèi)容,更多關(guān)于Vue引入ESLint代碼校驗(yàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3配置router路由并實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)功能
這篇文章主要介紹了vue3配置router路由并實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
Vue+Express實(shí)現(xiàn)登錄狀態(tài)權(quán)限驗(yàn)證的示例代碼
這篇文章主要介紹了Vue+Express實(shí)現(xiàn)登錄狀態(tài)權(quán)限驗(yàn)證的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05
vue復(fù)雜表格單元格合并根據(jù)數(shù)據(jù)動(dòng)態(tài)合并方式
這篇文章主要介紹了vue復(fù)雜表格單元格合并根據(jù)數(shù)據(jù)動(dòng)態(tài)合并方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02
Vue + Element-ui的下拉框el-select獲取額外參數(shù)詳解
這篇文章主要介紹了Vue + Element-ui的下拉框el-select獲取額外參數(shù)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Vue學(xué)習(xí)筆記進(jìn)階篇之vue-router安裝及使用方法
本篇文章主要介紹了Vue學(xué)習(xí)筆記進(jìn)階篇之vue-router安裝及使用方法,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
從零到一詳聊創(chuàng)建Vue工程及遇到的常見(jiàn)問(wèn)題
這篇文章主要介紹了從零到一詳聊如何創(chuàng)建Vue工程及遇到的常見(jiàn)問(wèn)題 ,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
vue?文件切片上傳的項(xiàng)目實(shí)現(xiàn)
本文主要介紹了vue?文件切片上傳的項(xiàng)目實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
解決el-tree數(shù)據(jù)回顯時(shí)子節(jié)點(diǎn)部分選中父節(jié)點(diǎn)都全選中的坑
本文主要介紹了解決el-tree數(shù)據(jù)回顯時(shí)子節(jié)點(diǎn)部分選中父節(jié)點(diǎn)都全選中的坑,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08

