一、vue3組件通信方式
1.1props
props可以實(shí)現(xiàn)父子組件通信,在vue3中我們可以通過defineProps獲取父組件傳遞的數(shù)據(jù)借浊。且在組件內(nèi)部不需要引入defineProps方法可以直接使用耘柱!
父組件給子組件傳遞數(shù)據(jù)
<Child info="我愛祖國" :money="money"></Child>
子組件獲取父組件傳遞數(shù)據(jù):方式1
let props = defineProps({
info:{
type:String,//接受的數(shù)據(jù)類型
default:'默認(rèn)參數(shù)',//接受默認(rèn)數(shù)據(jù)
},
money:{
type:Number,
default:0
}})
子組件獲取父組件傳遞數(shù)據(jù):方式2
let props = defineProps(["info",'money']);
子組件獲取到props數(shù)據(jù)就可以在模板中使用了,但是切記props是只讀的(只能讀取违寿,不能修改)
1.2自定義事件
在vue框架中事件分為兩種:一種是原生的DOM事件,另外一種自定義事件钢拧。
原生DOM事件可以讓用戶與網(wǎng)頁進(jìn)行交互雄妥,比如click最蕾、dbclick依溯、change老厌、mouseenter、mouseleave....
自定義事件可以實(shí)現(xiàn)子組件給父組件傳遞數(shù)據(jù)
1.2.1原生DOM事件
代碼如下:
<pre @click="handler">
我是祖國的老花骨朵
</pre>
當(dāng)前代碼級給pre標(biāo)簽綁定原生DOM事件點(diǎn)擊事件,默認(rèn)會給事件回調(diào)注入event事件對象黎炉。當(dāng)然點(diǎn)擊事件想注入多個(gè)參數(shù)可以按照下圖操作枝秤。但是切記注入的事件對象務(wù)必叫做$event.
<div @click="handler1(1,2,3,$event)">我要傳遞多個(gè)參數(shù)</div>
在vue3框架click、dbclick慷嗜、change(這類原生DOM事件),不管是在標(biāo)簽淀弹、自定義標(biāo)簽上(組件標(biāo)簽)都是原生DOM事件。
****
1.2.2自定義事件
自定義事件可以實(shí)現(xiàn)子組件給父組件傳遞數(shù)據(jù).在項(xiàng)目中是比較常用的庆械。
比如在父組件內(nèi)部給子組件(Event2)綁定一個(gè)自定義事件
<Event2 @xxx="handler3"></Event2>
在Event2子組件內(nèi)部觸發(fā)這個(gè)自定義事件
<template>
<div>
<h1>我是子組件2</h1>
<button @click="handler">點(diǎn)擊我觸發(fā)xxx自定義事件</button>
</div>
</template>
<script setup lang="ts">
let $emit = defineEmits(["xxx"]);
const handler = () => {
$emit("xxx", "法拉利", "茅臺");
};
</script>
<style scoped>
</style>
我們會發(fā)現(xiàn)在script標(biāo)簽內(nèi)部,使用了defineEmits方法薇溃,此方法是vue3提供的方法,不需要引入直接使用。defineEmits方法執(zhí)行缭乘,傳遞一個(gè)數(shù)組沐序,數(shù)組元素即為將來組件需要觸發(fā)的自定義事件類型,此方執(zhí)行會返回一個(gè)$emit方法用于觸發(fā)自定義事件堕绩。
當(dāng)點(diǎn)擊按鈕的時(shí)候策幼,事件回調(diào)內(nèi)部調(diào)用$emit方法去觸發(fā)自定義事件,第一個(gè)參數(shù)為觸發(fā)事件類型,第二個(gè)奴紧、三個(gè)特姐、N個(gè)參數(shù)即為傳遞給父組件的數(shù)據(jù)。
需要注意的是:代碼如下
<Event2 @xxx="handler3" @click="handler"></Event2>
正常說組件標(biāo)簽書寫@click應(yīng)該為原生DOM事件,但是如果子組件內(nèi)部通過defineEmits定義就變?yōu)樽远x事件了
let $emit = defineEmits(["xxx",'click']);
1.3全局事件總線
全局事件總線可以實(shí)現(xiàn)任意組件通信黍氮,在vue2中可以根據(jù)VM與VC關(guān)系推出全局事件總線唐含。
但是在vue3中沒有Vue構(gòu)造函數(shù),也就沒有Vue.prototype.以及組合式API寫法沒有this沫浆,
那么在Vue3想實(shí)現(xiàn)全局事件的總線功能就有點(diǎn)不現(xiàn)實(shí)啦捷枯,如果想在Vue3中使用全局事件總線功能
可以使用插件mitt實(shí)現(xiàn)。
mitt:官網(wǎng)地址:https://www.npmjs.com/package/mitt
1.4v-model
v-model指令可是收集表單數(shù)據(jù)(數(shù)據(jù)雙向綁定)件缸,除此之外它也可以實(shí)現(xiàn)父子組件數(shù)據(jù)同步铜靶。
而v-model實(shí)指利用props[modelValue]與自定義事件[update:modelValue]實(shí)現(xiàn)的喷户。
下方代碼:相當(dāng)于給組件Child傳遞一個(gè)props(modelValue)與綁定一個(gè)自定義事件update:modelValue
實(shí)現(xiàn)父子組件數(shù)據(jù)同步
<Child v-model="msg"></Child>
在vue3中一個(gè)組件可以通過使用多個(gè)v-model,讓父子組件多個(gè)數(shù)據(jù)同步,下方代碼相當(dāng)于給組件Child傳遞兩個(gè)props分別是pageNo與pageSize雷蹂,以及綁定兩個(gè)自定義事件update:pageNo與update:pageSize實(shí)現(xiàn)父子數(shù)據(jù)同步
<Child v-model:pageNo="msg" v-model:pageSize="msg1"></Child>
1.5useAttrs
在Vue3中可以利用useAttrs方法獲取組件的屬性與事件(包含:原生DOM事件或者自定義事件),次函數(shù)功能類似于Vue2框架中listeners方法。
比如:在父組件內(nèi)部使用一個(gè)子組件my-button
<my-button type="success" size="small" title='標(biāo)題' @click="handler"></my-button>
子組件內(nèi)部可以通過useAttrs方法獲取組件屬性與事件.因此你也發(fā)現(xiàn)了,它類似于props,可以接受父組件傳遞過來的屬性與屬性值匀哄。需要注意如果defineProps接受了某一個(gè)屬性,useAttrs方法返回的對象身上就沒有相應(yīng)屬性與屬性值松申。
<script setup lang="ts">
import {useAttrs} from 'vue';
let $attrs = useAttrs();
</script>
1.6ref與$parent
ref,提及到ref可能會想到它可以獲取元素的DOM或者獲取子組件實(shí)例的VC检眯。既然可以在父組件內(nèi)部通過ref獲取子組件實(shí)例VC,那么子組件內(nèi)部的方法與響應(yīng)式數(shù)據(jù)父組件可以使用的涩笤。
比如:在父組件掛載完畢獲取組件實(shí)例
父組件內(nèi)部代碼:
<template>
<div>
<h1>ref與$parent</h1>
<Son ref="son"></Son>
</div>
</template>
<script setup lang="ts">
import Son from "./Son.vue";
import { onMounted, ref } from "vue";
const son = ref();
onMounted(() => {
console.log(son.value);
});
</script>
但是需要注意嚼吞,如果想讓父組件獲取子組件的數(shù)據(jù)或者方法需要通過defineExpose對外暴露,因?yàn)関ue3中組件內(nèi)部的數(shù)據(jù)對外“關(guān)閉的”,外部不能訪問
<script setup lang="ts">
import { ref } from "vue";
//數(shù)據(jù)
let money = ref(1000);
//方法
const handler = ()=>{
}
defineExpose({
money,
handler
})
</script>
$parent可以獲取某一個(gè)組件的父組件實(shí)例VC,因此可以使用父組件內(nèi)部的數(shù)據(jù)與方法蹬碧。必須子組件內(nèi)部擁有一個(gè)按鈕點(diǎn)擊時(shí)候獲取父組件實(shí)例舱禽,當(dāng)然父組件的數(shù)據(jù)與方法需要通過defineExpose方法對外暴露
<button @click="handler($parent)">點(diǎn)擊我獲取父組件實(shí)例</button>
1.7provide與inject
provide[提供]
inject[注入]
vue3提供兩個(gè)方法provide與inject,可以實(shí)現(xiàn)隔輩組件傳遞參數(shù)
組件組件提供數(shù)據(jù):
provide方法用于提供數(shù)據(jù),此方法執(zhí)需要傳遞兩個(gè)參數(shù),分別提供數(shù)據(jù)的key與提供數(shù)據(jù)value
<script setup lang="ts">
import {provide} from 'vue'
provide('token','admin_token');
</script>
后代組件可以通過inject方法獲取數(shù)據(jù),通過key獲取存儲的數(shù)值
<script setup lang="ts">
import {inject} from 'vue'
let token = inject('token');
</script>
1.8pinia
pinia官網(wǎng):https://pinia.web3doc.top/
pinia也是集中式管理狀態(tài)容器,類似于vuex恩沽。但是核心概念沒有mutation誊稚、modules,使用方式參照官網(wǎng)
1.9slot
插槽:默認(rèn)插槽、具名插槽罗心、作用域插槽可以實(shí)現(xiàn)父子組件通信.
默認(rèn)插槽:
在子組件內(nèi)部的模板中書寫slot全局組件標(biāo)簽
<template>
<div>
<slot></slot>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
在父組件內(nèi)部提供結(jié)構(gòu):Todo即為子組件,在父組件內(nèi)部使用的時(shí)候里伯,在雙標(biāo)簽內(nèi)部書寫結(jié)構(gòu)傳遞給子組件
注意開發(fā)項(xiàng)目的時(shí)候默認(rèn)插槽一般只有一個(gè)
<Todo>
<h1>我是默認(rèn)插槽填充的結(jié)構(gòu)</h1>
</Todo>
具名插槽:
顧名思義,此插槽帶有名字在組件內(nèi)部留多個(gè)指定名字的插槽渤闷。
下面是一個(gè)子組件內(nèi)部,模板中留兩個(gè)插槽
<template>
<div>
<h1>todo</h1>
<slot name="a"></slot>
<slot name="b"></slot>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
父組件內(nèi)部向指定的具名插槽傳遞結(jié)構(gòu)疾瓮。需要注意v-slot:可以替換為#
<template>
<div>
<h1>slot</h1>
<Todo>
<template v-slot:a> //可以用#a替換
<div>填入組件A部分的結(jié)構(gòu)</div>
</template>
<template v-slot:b>//可以用#b替換
<div>填入組件B部分的結(jié)構(gòu)</div>
</template>
</Todo>
</div>
</template>
<script setup lang="ts">
import Todo from "./Todo.vue";
</script>
<style scoped>
</style>
作用域插槽
作用域插槽:可以理解為,子組件數(shù)據(jù)由父組件提供飒箭,但是子組件內(nèi)部決定不了自身結(jié)構(gòu)與外觀(樣式)
子組件Todo代碼如下:
<template>
<div>
<h1>todo</h1>
<ul>
<!--組件內(nèi)部遍歷數(shù)組-->
<li v-for="(item,index) in todos" :key="item.id">
<!--作用域插槽將數(shù)據(jù)回傳給父組件-->
<slot :$row="item" :$index="index"></slot>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
defineProps(['todos']);//接受父組件傳遞過來的數(shù)據(jù)
</script>
<style scoped>
</style>
父組件內(nèi)部代碼如下:
<template>
<div>
<h1>slot</h1>
<Todo :todos="todos">
<template v-slot="{$row,$index}">
<!--父組件決定子組件的結(jié)構(gòu)與外觀-->
<span :style="{color:$row.done?'green':'red'}">{{$row.title}}</span>
</template>
</Todo>
</div>
</template>
<script setup lang="ts">
import Todo from "./Todo.vue";
import { ref } from "vue";
//父組件內(nèi)部數(shù)據(jù)
let todos = ref([
{ id: 1, title: "吃飯", done: true },
{ id: 2, title: "睡覺", done: false },
{ id: 3, title: "打豆豆", done: true },
]);
</script>
<style scoped>
</style>
二狼电、搭建后臺管理系統(tǒng)模板
2.1項(xiàng)目初始化
今天來帶大家從0開始搭建一個(gè)vue3版本的后臺管理系統(tǒng)。一個(gè)項(xiàng)目要有統(tǒng)一的規(guī)范补憾,需要使用eslint+stylelint+prettier來對我們的代碼質(zhì)量做檢測和修復(fù)漫萄,需要使用husky來做commit攔截,需要使用commitlint來統(tǒng)一提交規(guī)范盈匾,需要使用preinstall來統(tǒng)一包管理工具腾务。
下面我們就用這一套規(guī)范來初始化我們的項(xiàng)目,集成一個(gè)規(guī)范的模版削饵。
2.1.1環(huán)境準(zhǔn)備
- node v16.14.2
- pnpm 8.0.0
2.1.2初始化項(xiàng)目
本項(xiàng)目使用vite進(jìn)行構(gòu)建岩瘦,vite官方中文文檔參考:cn.vitejs.dev/guide/
pnpm:performant npm ,意味“高性能的 npm”窿撬。pnpm由npm/yarn衍生而來启昧,解決了npm/yarn內(nèi)部潛在的bug,極大的優(yōu)化了性能劈伴,擴(kuò)展了使用場景密末。被譽(yù)為“最先進(jìn)的包管理工具”
pnpm安裝指令
npm i -g pnpm
項(xiàng)目初始化命令:
pnpm create vite
進(jìn)入到項(xiàng)目根目錄pnpm install安裝全部依賴.安裝完依賴運(yùn)行程序:pnpm run dev
運(yùn)行完畢項(xiàng)目跑在http://127.0.0.1:5173/,可以訪問你得項(xiàng)目啦
2.2項(xiàng)目配置
一、eslint配置
eslint中文官網(wǎng):http://eslint.cn/
ESLint最初是由Nicholas C. Zakas 于2013年6月創(chuàng)建的開源項(xiàng)目。它的目標(biāo)是提供一個(gè)插件化的javascript代碼檢測工具
首先安裝eslint
pnpm i eslint -D
生成配置文件:.eslint.cjs
npx eslint --init
.eslint.cjs配置文件
module.exports = {
//運(yùn)行環(huán)境
"env": {
"browser": true,//瀏覽器端
"es2021": true,//es2021
},
//規(guī)則繼承
"extends": [
//全部規(guī)則默認(rèn)是關(guān)閉的,這個(gè)配置項(xiàng)開啟推薦規(guī)則,推薦規(guī)則參照文檔
//比如:函數(shù)不能重名严里、對象不能出現(xiàn)重復(fù)key
"eslint:recommended",
//vue3語法規(guī)則
"plugin:vue/vue3-essential",
//ts語法規(guī)則
"plugin:@typescript-eslint/recommended"
],
//要為特定類型的文件指定處理器
"overrides": [
],
//指定解析器:解析器
//Esprima 默認(rèn)解析器
//Babel-ESLint babel解析器
//@typescript-eslint/parser ts解析器
"parser": "@typescript-eslint/parser",
//指定解析器選項(xiàng)
"parserOptions": {
"ecmaVersion": "latest",//校驗(yàn)ECMA最新版本
"sourceType": "module"http://設(shè)置為"script"(默認(rèn))新啼,或者"module"代碼在ECMAScript模塊中
},
//ESLint支持使用第三方插件。在使用插件之前刹碾,您必須使用npm安裝它
//該eslint-plugin-前綴可以從插件名稱被省略
"plugins": [
"vue",
"@typescript-eslint"
],
//eslint規(guī)則
"rules": {
}
}
1.1vue3環(huán)境代碼校驗(yàn)插件
# 讓所有與prettier規(guī)則存在沖突的Eslint rules失效燥撞,并使用prettier進(jìn)行代碼檢查
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
# 運(yùn)行更漂亮的Eslint,使prettier規(guī)則優(yōu)先級更高迷帜,Eslint優(yōu)先級低
"eslint-plugin-prettier": "^4.2.1",
# vue.js的Eslint插件(查找vue語法錯誤物舒,發(fā)現(xiàn)錯誤指令,查找違規(guī)風(fēng)格指南
"eslint-plugin-vue": "^9.9.0",
# 該解析器允許使用Eslint校驗(yàn)所有babel code
"@babel/eslint-parser": "^7.19.1",
安裝指令
pnpm install -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin-node @babel/eslint-parser
1.2修改.eslintrc.cjs配置文件
// @see https://eslint.bootcss.com/docs/rules/
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
/* 指定如何解析語法 */
parser: 'vue-eslint-parser',
/** 優(yōu)先級低于 parse 的語法解析配置 */
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: '@typescript-eslint/parser',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
/* 繼承已有的規(guī)則 */
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
plugins: ['vue', '@typescript-eslint'],
/*
* "off" 或 0 ==> 關(guān)閉規(guī)則
* "warn" 或 1 ==> 打開的規(guī)則作為警告(不影響代碼執(zhí)行)
* "error" 或 2 ==> 規(guī)則作為一個(gè)錯誤(代碼不能執(zhí)行戏锹,界面報(bào)錯)
*/
rules: {
// eslint(https://eslint.bootcss.com/docs/rules/)
'no-var': 'error', // 要求使用 let 或 const 而不是 var
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允許多個(gè)空行
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unexpected-multiline': 'error', // 禁止空余的多行
'no-useless-escape': 'off', // 禁止不必要的轉(zhuǎn)義字符
// typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': 'error', // 禁止定義未使用的變量
'@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 類型
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定義 TypeScript 模塊和命名空間冠胯。
'@typescript-eslint/semi': 'off',
// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
'vue/multi-word-component-names': 'off', // 要求組件名稱始終為 “-” 鏈接的單詞
'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的變量<template>被標(biāo)記為未使用
'vue/no-mutating-props': 'off', // 不允許組件 prop的改變
'vue/attribute-hyphenation': 'off', // 對模板中的自定義組件強(qiáng)制執(zhí)行屬性命名樣式
},
}
1.3.eslintignore忽略文件
# npm包
/node_modules
package-lock.json
# build產(chǎn)物
/dist
/types
# eslint
.eslintcache
# jest
/coverage
# docs api文檔
/docs
# webpack 配置
/scripts
#webstorm
.idea
# 自動引入生成文件
auto-imports.d.ts
components.d.ts
1.4運(yùn)行腳本
package.json新增兩個(gè)運(yùn)行腳本
"scripts": {
"lint": "eslint src",
"fix": "eslint src --fix",
}
二、配置prettier
有了eslint景用,為什么還要有prettier涵叮?eslint針對的是javascript,他是一個(gè)檢測工具伞插,包含js語法以及少部分格式問題,在eslint看來盾碗,語法對了就能保證代碼正常運(yùn)行媚污,格式問題屬于其次;
而prettier屬于格式化工具廷雅,它看不慣格式不統(tǒng)一耗美,所以它就把eslint沒干好的事接著干,另外航缀,prettier支持
包含js在內(nèi)的多種語言商架。
總結(jié)起來,eslint和prettier這倆兄弟一個(gè)保證js代碼質(zhì)量芥玉,一個(gè)保證代碼美觀蛇摸。
2.1安裝依賴包
pnpm install -D eslint-plugin-prettier prettier eslint-config-prettier
2.2.prettierrc.json添加規(guī)則
{
"singleQuote": true,
"semi": false,
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "ignore",
"endOfLine": "auto",
"trailingComma": "all",
"tabWidth": 2
}
2.3.prettierignore忽略文件
/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*
通過pnpm run lint去檢測語法,如果出現(xiàn)不規(guī)范格式,通過pnpm run fix 修改
三灿巧、配置stylelint
stylelint為css的lint工具赶袄。可格式化css代碼抠藕,檢查css語法錯誤與不合理的寫法饿肺,指定css書寫順序等。
我們的項(xiàng)目中使用scss作為預(yù)處理器盾似,安裝以下依賴:
pnpm add sass sass-loader stylelint postcss postcss-scss postcss-html stylelint-config-prettier stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-scss stylelint-order stylelint-config-standard-scss -D
3.1.stylelintrc.cjs
配置文件
官網(wǎng):https://stylelint.bootcss.com/
// @see https://stylelint.bootcss.com/
module.exports = {
extends: [
'stylelint-config-standard', // 配置stylelint拓展插件
'stylelint-config-html/vue', // 配置 vue 中 template 樣式格式化
'stylelint-config-standard-scss', // 配置stylelint scss插件
'stylelint-config-recommended-vue/scss', // 配置 vue 中 scss 樣式格式化
'stylelint-config-recess-order', // 配置stylelint css屬性書寫順序插件,
'stylelint-config-prettier', // 配置stylelint和prettier兼容
],
overrides: [
{
files: ['**/*.(scss|css|vue|html)'],
customSyntax: 'postcss-scss',
},
{
files: ['**/*.(html|vue)'],
customSyntax: 'postcss-html',
},
],
ignoreFiles: [
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
'**/*.yaml',
],
/**
* null => 關(guān)閉該規(guī)則
* always => 必須
*/
rules: {
'value-keyword-case': null, // 在 css 中使用 v-bind敬辣,不報(bào)錯
'no-descending-specificity': null, // 禁止在具有較高優(yōu)先級的選擇器后出現(xiàn)被其覆蓋的較低優(yōu)先級的選擇器
'function-url-quotes': 'always', // 要求或禁止 URL 的引號 "always(必須加上引號)"|"never(沒有引號)"
'no-empty-source': null, // 關(guān)閉禁止空源碼
'selector-class-pattern': null, // 關(guān)閉強(qiáng)制選擇器類名的格式
'property-no-unknown': null, // 禁止未知的屬性(true 為不允許)
'block-opening-brace-space-before': 'always', //大括號之前必須有一個(gè)空格或不能有空白符
'value-no-vendor-prefix': null, // 關(guān)閉 屬性值前綴 --webkit-box
'property-no-vendor-prefix': null, // 關(guān)閉 屬性前綴 -webkit-mask
'selector-pseudo-class-no-unknown': [
// 不允許未知的選擇器
true,
{
ignorePseudoClasses: ['global', 'v-deep', 'deep'], // 忽略屬性,修改element默認(rèn)樣式的時(shí)候能使用到
},
],
},
}
3.2.stylelintignore忽略文件
/node_modules/*
/dist/*
/html/*
/public/*
3.3運(yùn)行腳本
"scripts": {
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
}
最后配置統(tǒng)一的prettier來格式化我們的js和css,html代碼
"scripts": {
"dev": "vite --open",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint src",
"fix": "eslint src --fix",
"format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
"lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
},
當(dāng)我們運(yùn)行pnpm run format
的時(shí)候溉跃,會把代碼直接格式化
四汰聋、配置husky
在上面我們已經(jīng)集成好了我們代碼校驗(yàn)工具,但是需要每次手動的去執(zhí)行命令才會格式化我們的代碼喊积。如果有人沒有格式化就提交了遠(yuǎn)程倉庫中烹困,那這個(gè)規(guī)范就沒什么用。所以我們需要強(qiáng)制讓開發(fā)人員按照代碼規(guī)范來提交乾吻。
要做到這件事情髓梅,就需要利用husky在代碼提交之前觸發(fā)git hook(git在客戶端的鉤子),然后執(zhí)行pnpm run format
來自動的格式化我們的代碼绎签。
安裝husky
pnpm install -D husky
執(zhí)行
npx husky-init
會在根目錄下生成個(gè)一個(gè).husky目錄枯饿,在這個(gè)目錄下面會有一個(gè)pre-commit文件,這個(gè)文件里面的命令在我們執(zhí)行commit的時(shí)候就會執(zhí)行
在.husky/pre-commit
文件添加如下命令:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm run format
當(dāng)我們對代碼進(jìn)行commit操作的時(shí)候诡必,就會執(zhí)行命令奢方,對代碼進(jìn)行格式化,然后再提交爸舒。
五蟋字、配置commitlint
對于我們的commit信息,也是有統(tǒng)一規(guī)范的扭勉,不能隨便寫,要讓每個(gè)人都按照統(tǒng)一的標(biāo)準(zhǔn)來執(zhí)行鹊奖,我們可以利用commitlint來實(shí)現(xiàn)。
安裝包
pnpm add @commitlint/config-conventional @commitlint/cli -D
添加配置文件涂炎,新建commitlint.config.cjs
(注意是cjs)忠聚,然后添加下面的代碼:
module.exports = {
extends: ['@commitlint/config-conventional'],
// 校驗(yàn)規(guī)則
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'chore',
'revert',
'build',
],
],
'type-case': [0],
'type-empty': [0],
'scope-empty': [0],
'scope-case': [0],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never'],
'header-max-length': [0, 'always', 72],
},
}
在package.json
中配置scripts命令
# 在scrips中添加下面的代碼
{
"scripts": {
"commitlint": "commitlint --config commitlint.config.cjs -e -V"
},
}
配置結(jié)束,現(xiàn)在當(dāng)我們填寫commit
信息的時(shí)候唱捣,前面就需要帶著下面的subject
'feat',//新特性两蟀、新功能
'fix',//修改bug
'docs',//文檔修改
'style',//代碼格式修改, 注意不是 css 修改
'refactor',//代碼重構(gòu)
'perf',//優(yōu)化相關(guān),比如提升性能震缭、體驗(yàn)
'test',//測試用例修改
'chore',//其他修改, 比如改變構(gòu)建流程赂毯、或者增加依賴庫、工具等
'revert',//回滾到上一個(gè)版本
'build',//編譯相關(guān)的修改蛀序,例如發(fā)布版本欢瞪、對項(xiàng)目構(gòu)建或者依賴的改動
配置husky
npx husky add .husky/commit-msg
在生成的commit-msg文件中添加下面的命令
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm commitlint
當(dāng)我們 commit 提交信息時(shí),就不能再隨意寫了徐裸,必須是 git commit -m 'fix: xxx' 符合類型的才可以遣鼓,需要注意的是類型的后面需要用英文的 :,并且冒號后面是需要空一格的重贺,這個(gè)是不能省略的
六骑祟、強(qiáng)制使用pnpm包管理器工具
團(tuán)隊(duì)開發(fā)項(xiàng)目的時(shí)候回懦,需要統(tǒng)一包管理器工具,因?yàn)椴煌芾砥鞴ぞ呦螺d同一個(gè)依賴,可能版本不一樣,
導(dǎo)致項(xiàng)目出現(xiàn)bug問題,因此包管理器工具需要統(tǒng)一管理!4纹蟆怯晕!
在根目錄創(chuàng)建scritps/preinstall.js
文件,添加下面的內(nèi)容
if (!/pnpm/.test(process.env.npm_execpath || '')) {
console.warn(
`\u001b[33mThis repository must using pnpm as the package manager ` +
` for scripts to work properly.\u001b[39m\n`,
)
process.exit(1)
}
配置命令
"scripts": {
"preinstall": "node ./scripts/preinstall.js"
}
當(dāng)我們使用npm或者yarn來安裝包的時(shí)候缸棵,就會報(bào)錯了舟茶。原理就是在install的時(shí)候會觸發(fā)preinstall(npm提供的生命周期鉤子)這個(gè)文件里面的代碼。
三堵第、項(xiàng)目集成
3.1集成element-plus
硅谷甄選運(yùn)營平臺,UI組件庫采用的element-plus吧凉,因此需要集成element-plus插件!Lぶ尽阀捅!
官網(wǎng)地址:https://element-plus.gitee.io/zh-CN/
pnpm install element-plus @element-plus/icons-vue
入口文件main.ts全局安裝element-plus,element-plus默認(rèn)支持語言英語設(shè)置為中文
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'
//@ts-ignore忽略當(dāng)前文件ts類型的檢測否則有紅色提示(打包會失敗)
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
app.use(ElementPlus, {
locale: zhCn
})
Element Plus全局組件類型聲明
// tsconfig.json
{
"compilerOptions": {
// ...
"types": ["element-plus/global"]
}
}
配置完畢可以測試element-plus組件與圖標(biāo)的使用.
3.2src別名的配置
在開發(fā)項(xiàng)目的時(shí)候文件與文件關(guān)系可能很復(fù)雜,因此我們需要給src文件夾配置一個(gè)別名U胗唷K潜伞!
// vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve("./src") // 相對路徑別名配置圆雁,使用 @ 代替 src
}
}
})
TypeScript 編譯配置
// tsconfig.app.json
{
"compilerOptions": {
"baseUrl": "./", // 解析非相對模塊的基地址忍级,默認(rèn)是當(dāng)前目錄
"paths": { //路徑映射,相對于baseUrl
"@/*": ["src/*"]
}
}
}
3.3環(huán)境變量的配置
項(xiàng)目開發(fā)過程中摸柄,至少會經(jīng)歷開發(fā)環(huán)境颤练、測試環(huán)境和生產(chǎn)環(huán)境(即正式環(huán)境)三個(gè)階段。不同階段請求的狀態(tài)(如接口地址等)不盡相同驱负,若手動切換接口地址是相當(dāng)繁瑣且易出錯的。于是環(huán)境變量配置的需求就應(yīng)運(yùn)而生患雇,我們只需做簡單的配置跃脊,把環(huán)境狀態(tài)切換的工作交給代碼。
開發(fā)環(huán)境(development)
顧名思義苛吱,開發(fā)使用的環(huán)境酪术,每位開發(fā)人員在自己的dev分支上干活,開發(fā)到一定程度翠储,同事會合并代碼绘雁,進(jìn)行聯(lián)調(diào)。
測試環(huán)境(testing)
測試同事干活的環(huán)境啦援所,一般會由測試同事自己來部署庐舟,然后在此環(huán)境進(jìn)行測試
生產(chǎn)環(huán)境(production)
生產(chǎn)環(huán)境是指正式提供對外服務(wù)的,一般會關(guān)掉錯誤報(bào)告住拭,打開錯誤日志挪略。(正式提供給客戶使用的環(huán)境历帚。)
注意:一般情況下,一個(gè)環(huán)境對應(yīng)一臺服務(wù)器,也有的公司開發(fā)與測試環(huán)境是一臺服務(wù)器8苡椤M炖巍!
項(xiàng)目根目錄分別添加 開發(fā)摊求、生產(chǎn)和測試環(huán)境的文件!
.env.development
.env.production
.env.test
文件內(nèi)容
# 變量必須以 VITE_ 為前綴才能暴露給外部讀取
NODE_ENV = 'development'
VITE_APP_TITLE = '硅谷甄選運(yùn)營平臺'
VITE_APP_BASE_API = '/dev-api'
NODE_ENV = 'production'
VITE_APP_TITLE = '硅谷甄選運(yùn)營平臺'
VITE_APP_BASE_API = '/prod-api'
# 變量必須以 VITE_ 為前綴才能暴露給外部讀取
NODE_ENV = 'test'
VITE_APP_TITLE = '硅谷甄選運(yùn)營平臺'
VITE_APP_BASE_API = '/test-api'
配置運(yùn)行命令:package.json
"scripts": {
"dev": "vite --open",
"build:test": "vue-tsc && vite build --mode test",
"build:pro": "vue-tsc && vite build --mode production",
"preview": "vite preview"
},
通過import.meta.env獲取環(huán)境變量
3.4SVG圖標(biāo)配置
在開發(fā)項(xiàng)目的時(shí)候經(jīng)常會用到svg矢量圖,而且我們使用SVG以后禽拔,頁面上加載的不再是圖片資源,
這對頁面性能來說是個(gè)很大的提升,而且我們SVG文件比img要小的很多室叉,放在項(xiàng)目中幾乎不占用資源睹栖。
安裝SVG依賴插件
pnpm install vite-plugin-svg-icons -D
在vite.config.ts
中配置插件
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
return {
plugins: [
createSvgIconsPlugin({
// Specify the icon folder to be cached
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// Specify symbolId format
symbolId: 'icon-[dir]-[name]',
}),
],
}
}
入口文件導(dǎo)入
import 'virtual:svg-icons-register'
3.4.1svg封裝為全局組件
因?yàn)轫?xiàng)目很多模塊需要使用圖標(biāo),因此把它封裝為全局組件!L荨磨淌!
在src/components目錄下創(chuàng)建一個(gè)SvgIcon組件:代表如下
<template>
<div>
<svg :style="{ width: width, height: height }">
<use :xlink:href="prefix + name" :fill="color"></use>
</svg>
</div>
</template>
<script setup lang="ts">
defineProps({
//xlink:href屬性值的前綴
prefix: {
type: String,
default: '#icon-'
},
//svg矢量圖的名字
name: String,
//svg圖標(biāo)的顏色
color: {
type: String,
default: ""
},
//svg寬度
width: {
type: String,
default: '16px'
},
//svg高度
height: {
type: String,
default: '16px'
}
})
</script>
<style scoped></style>
在src文件夾目錄下創(chuàng)建一個(gè)index.ts文件:用于注冊components文件夾內(nèi)部全部全局組件!T湓ā梁只!
import SvgIcon from './SvgIcon/index.vue';
import type { App, Component } from 'vue';
const components: { [name: string]: Component } = { SvgIcon };
export default {
install(app: App) {
Object.keys(components).forEach((key: string) => {
app.component(key, components[key]);
})
}
}
在入口文件引入src/index.ts文件,通過app.use方法安裝自定義插件
import gloablComponent from './components/index';
app.use(gloablComponent);
3.5集成sass
我們目前在組件內(nèi)部已經(jīng)可以使用scss樣式,因?yàn)樵谂渲胹tyleLint工具的時(shí)候,項(xiàng)目當(dāng)中已經(jīng)安裝過sass sass-loader,因此我們再組件內(nèi)可以使用scss語法0T唷L侣唷!需要加上lang="scss"
<style scoped lang="scss"></style>
接下來我們?yōu)轫?xiàng)目添加一些全局的樣式
在src/styles目錄下創(chuàng)建一個(gè)index.scss文件彩掐,當(dāng)然項(xiàng)目中需要用到清除默認(rèn)樣式构舟,因此在index.scss引入reset.scss
@import reset.scss
在入口文件引入
import '@/styles'
但是你會發(fā)現(xiàn)在src/styles/index.scss全局樣式文件中沒有辦法使用.
在style/variable.scss創(chuàng)建一個(gè)variable.scss文件!
在vite.config.ts文件配置如下:
export default defineConfig((config) => {
css: {
preprocessorOptions: {
scss: {
javascriptEnabled: true,
additionalData: '@import "./src/styles/variable.scss";',
},
},
},
}
}
@import "./src/styles/variable.less";
后面的;
不要忘記堵幽,不然會報(bào)錯!
配置完畢你會發(fā)現(xiàn)scss提供這些全局變量可以在組件樣式中使用了9烦!朴下!
3.6mock數(shù)據(jù)
安裝依賴:https://www.npmjs.com/package/vite-plugin-mock
pnpm install -D vite-plugin-mock mockjs
在 vite.config.js 配置文件啟用插件努咐。
import { UserConfigExport, ConfigEnv } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
import vue from '@vitejs/plugin-vue'
export default ({ command })=> {
return {
plugins: [
vue(),
viteMockServe({
localEnabled: command === 'serve',
}),
],
}
}
在根目錄創(chuàng)建mock文件夾:去創(chuàng)建我們需要mock數(shù)據(jù)與接口!E闺省渗稍!
在mock文件夾內(nèi)部創(chuàng)建一個(gè)user.ts文件
//用戶信息數(shù)據(jù)
function createUserList() {
return [
{
userId: 1,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'admin',
password: '111111',
desc: '平臺管理員',
roles: ['平臺管理員'],
buttons: ['cuser.detail'],
routes: ['home'],
token: 'Admin Token',
},
{
userId: 2,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'system',
password: '111111',
desc: '系統(tǒng)管理員',
roles: ['系統(tǒng)管理員'],
buttons: ['cuser.detail', 'cuser.user'],
routes: ['home'],
token: 'System Token',
},
]
}
export default [
// 用戶登錄接口
{
url: '/api/user/login',//請求地址
method: 'post',//請求方式
response: ({ body }) => {
//獲取請求體攜帶過來的用戶名與密碼
const { username, password } = body;
//調(diào)用獲取用戶信息函數(shù),用于判斷是否有此用戶
const checkUser = createUserList().find(
(item) => item.username === username && item.password === password,
)
//沒有用戶返回失敗信息
if (!checkUser) {
return { code: 201, data: { message: '賬號或者密碼不正確' } }
}
//如果有返回成功信息
const { token } = checkUser
return { code: 200, data: { token } }
},
},
// 獲取用戶信息
{
url: '/api/user/info',
method: 'get',
response: (request) => {
//獲取請求頭攜帶token
const token = request.headers.token;
//查看用戶信息是否包含有次token用戶
const checkUser = createUserList().find((item) => item.token === token)
//沒有返回失敗的信息
if (!checkUser) {
return { code: 201, data: { message: '獲取用戶信息失敗' } }
}
//如果有返回成功信息
return { code: 200, data: {checkUser} }
},
},
]
安裝axios
pnpm install axios
最后通過axios測試接口!M爬摹竿屹!
3.7axios二次封裝
在開發(fā)項(xiàng)目的時(shí)候避免不了與后端進(jìn)行交互,因此我們需要使用axios插件實(shí)現(xiàn)發(fā)送網(wǎng)絡(luò)請求。在開發(fā)項(xiàng)目的時(shí)候
我們經(jīng)常會把a(bǔ)xios進(jìn)行二次封裝灸姊。
目的:
1:使用請求攔截器拱燃,可以在請求攔截器中處理一些業(yè)務(wù)(開始進(jìn)度條、請求頭攜帶公共參數(shù))
2:使用響應(yīng)攔截器厨钻,可以在響應(yīng)攔截器中處理一些業(yè)務(wù)(進(jìn)度條結(jié)束扼雏、簡化服務(wù)器返回的數(shù)據(jù)坚嗜、處理http網(wǎng)絡(luò)錯誤)
在根目錄下創(chuàng)建utils/request.ts
import axios from "axios";
import { ElMessage } from "element-plus";
//創(chuàng)建axios實(shí)例
let request = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000
})
//請求攔截器
request.interceptors.request.use(config => {
return config;
});
//響應(yīng)攔截器
request.interceptors.response.use((response) => {
return response.data;
}, (error) => {
//處理網(wǎng)絡(luò)錯誤
let msg = '';
let status = error.response.status;
switch (status) {
case 401:
msg = "token過期";
break;
case 403:
msg = '無權(quán)訪問';
break;
case 404:
msg = "請求地址錯誤";
break;
case 500:
msg = "服務(wù)器出現(xiàn)問題";
break;
default:
msg = "無網(wǎng)絡(luò)";
}
ElMessage({
type: 'error',
message: msg
})
return Promise.reject(error);
});
export default request;
3.8API接口統(tǒng)一管理
在開發(fā)項(xiàng)目的時(shí)候,接口可能很多需要統(tǒng)一管理。在src目錄下去創(chuàng)建api文件夾去統(tǒng)一管理項(xiàng)目的接口诗充;
比如:下面方式
//統(tǒng)一管理咱們項(xiàng)目用戶相關(guān)的接口
import request from '@/utils/request'
import type {
loginFormData,
loginResponseData,
userInfoReponseData,
} from './type'
//項(xiàng)目用戶相關(guān)的請求地址
enum API {
LOGIN_URL = '/admin/acl/index/login',
USERINFO_URL = '/admin/acl/index/info',
LOGOUT_URL = '/admin/acl/index/logout',
}
//登錄接口
export const reqLogin = (data: loginFormData) =>
request.post<any, loginResponseData>(API.LOGIN_URL, data)
//獲取用戶信息
export const reqUserInfo = () =>
request.get<any, userInfoReponseData>(API.USERINFO_URL)
//退出登錄
export const reqLogout = () => request.post<any, any>(API.LOGOUT_URL)