最近嘗試上手 Vue3+TS+Vite
,對(duì)比起 Vue2
有些不適應(yīng)杜窄,但還是真香~
上手前先說(shuō)下 Vue3 的一些變化吧~
Vue3 的變化
Vue3 帶來(lái)的變化主要有以下幾個(gè)方面:
-
使用層面
- 對(duì)比起 Vue2 啟動(dòng)速度快很多利耍,新項(xiàng)目從 1s 升級(jí)到不到 500ms
-
vite.config.ts
配置文件修改后無(wú)需重啟服務(wù)就能更新
-
代碼層面
- 函數(shù)式編程蚌本,方便組合邏輯,如mixin容易命名沖突隘梨,數(shù)據(jù)來(lái)源不清晰
- 新增
ref
,reative
API定義變量 - 更好的
ts
支持 - 組件文件中
template
模板內(nèi)無(wú)需用根節(jié)點(diǎn)標(biāo)簽包著組件元素
-
底層設(shè)計(jì)
- 雙向數(shù)據(jù)綁定從
defineProperty
for in 循環(huán)變量改成proxy
舷嗡。defineProperty
是改變?cè)瓕?duì)象屬性標(biāo)簽轴猎;而proxy
未改變?cè)瓕?duì)象,而是產(chǎn)生新的代理對(duì)象进萄,js 引擎更喜歡穩(wěn)定的對(duì)象 - 重新定義
vdom
對(duì)比思路:- 區(qū)分動(dòng)靜態(tài) dom捻脖,只對(duì)比動(dòng)態(tài)數(shù)據(jù) dom,用block 標(biāo)記動(dòng)態(tài)標(biāo)簽內(nèi)部的靜態(tài)標(biāo)簽
- 使用最長(zhǎng)遞增子序列算法中鼠,找到所有不需要移動(dòng)的元素
- compile 編譯優(yōu)化可婶,把大量計(jì)算放在 node 層,最后瀏覽器只需執(zhí)行最少的代碼
- 雙向數(shù)據(jù)綁定從
底層設(shè)計(jì)層面的改變決定了 vue3 比 vue2 更快
下面介紹上手步驟~ (官網(wǎng)鏈接)
創(chuàng)建項(xiàng)目
使用 vite
命令創(chuàng)建初始項(xiàng)目
# npm 6.x
npm create vite@latest my-vue-app --template vue
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue
cd my-vue-app
npm install
npm run dev
Vite
配置
功能一致的配置大多跟 vue-cli
配置大同小異援雇,不過(guò)多贅述
resolve
resolve.alias:當(dāng)使用文件系統(tǒng)路徑的別名時(shí)矛渴,請(qǐng)始終使用絕對(duì)路徑。相對(duì)路徑的別名值會(huì)原封不動(dòng)地被使用惫搏,因此無(wú)法被正常解析具温。
/* vite.config.ts */
resolve: {
//文件系統(tǒng)路徑的別名, 絕對(duì)路徑
alias: {
"@": path.resolve(__dirname, "src"),
}
}
sass配置
安裝sass依賴和配置 vite.config.ts
預(yù)定義全局變量
npm i sass -D
/* vite.config.ts */
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "./src/assets/scss/var.scss";'
}
}
}
開啟服務(wù)配置
開啟 http
服務(wù)
/* vite.config.ts */
server:{
host: 'dev.moon.cn',
port: 3000
}
開啟 https
服務(wù)
/* vite.config.ts */
let httpsConfig = {
key: fs.readFileSync("C:/Users/ca/wps.cn/_wildcard.wps.cn+3-key.pem"),
cert: fs.readFileSync("C:/Users/ca/wps.cn/_wildcard.wps.cn+3.pem")
};
server:{
https: httpsConfig,
host: 'dev.moon.cn',
port: 443,
open: true
}
預(yù)構(gòu)建依賴優(yōu)化
默認(rèn)情況下,Vite 會(huì)抓取你的 index.html 來(lái)檢測(cè)需要預(yù)構(gòu)建的依賴項(xiàng)筐赔。如果指定了 build.rollupOptions.input
铣猩,Vite 將轉(zhuǎn)而去抓取這些入口點(diǎn)。
optimizeDeps.include
默認(rèn)情況下茴丰,不在 node_modules
中的达皿,鏈接的包不會(huì)被預(yù)構(gòu)建天吓。使用此選項(xiàng)可強(qiáng)制預(yù)構(gòu)建鏈接的包。
/* vite.config.ts */
optimizeDeps: {
include: ['axios'],
},
optimizeDeps.exclude
在預(yù)構(gòu)建中強(qiáng)制排除的依賴項(xiàng)峦椰。
eslint 配置
vue3
和 ts
的 eslint
配置需另外自行配置失仁,除了需配置 eslint 規(guī)則外還需調(diào)整 vite 的相關(guān)配置,感興趣的話可以看看我另一篇文章(內(nèi)附配置解析)们何,或者直接看完整源碼萄焦,這里不做贅述。
TypeScript
TypeScript 是添加了類型系統(tǒng)的 JavaScript冤竹,適用于任何規(guī)模的項(xiàng)目拂封,在編譯階段進(jìn)行類型檢查。
基礎(chǔ)知識(shí)可直接看中文文檔鹦蠕,英文比較好的小伙伴可以直接看官方文檔冒签,這里不做贅述,這里分享一些值得說(shuō)的地方
類型/接口/泛型
類型:類型(type)不是定義一個(gè)新類型钟病,而是一個(gè)類型別名萧恕,使類型更具體化
接口:接口(interface)則是描述一個(gè)對(duì)象的形狀,對(duì)值所具有的結(jié)構(gòu)進(jìn)行類型檢查肠阱。接口的作用類似于抽象類票唆,不同點(diǎn)在于接口中的所有方法和屬性都是沒(méi)有實(shí)值的,換句話說(shuō)接口中的所有方法都是抽象方法屹徘。接口主要負(fù)責(zé)定義一個(gè)類的結(jié)構(gòu)走趋,接口可以去限制一個(gè)對(duì)象的接口,對(duì)象只有包含接口中定義的所有屬性和方法時(shí)才能匹配接口噪伊。同時(shí)簿煌,可以讓一個(gè)類去實(shí)現(xiàn)接口,實(shí)現(xiàn)接口時(shí)類中要保護(hù)接口中的所有屬性鉴吹。
泛型:支持多種數(shù)據(jù)結(jié)構(gòu)姨伟,有函數(shù)泛型,類泛型豆励,接口泛型等夺荒。
你可能想問(wèn)什么時(shí)候用類型,什么時(shí)候用接口肆糕?Typescript團(tuán)隊(duì)的建議是
可以使用接口就盡量使用接口般堆,因?yàn)榻涌诟`活,更容易處理
很多時(shí)候 interface 和 type 是相同的诚啃,但有一個(gè)明顯區(qū)別在于 interface 可以重復(fù)定義淮摔,類型注解會(huì)累加,而 type 重復(fù)定義會(huì)報(bào)錯(cuò)
類型聲明
類型聲明(Type Declaration) 或者類型定義(Type Definition) 文件是一個(gè)以.d.ts
作為文件后綴名的TypeScript文件始赎。文件中只包含與類型相關(guān)的代碼和橙,不包含邏輯代碼仔燕,它們的作用旨在為開發(fā)者提供類型信息,所以它們只在開發(fā)階段起作用魔招。
typescript編譯后會(huì)將類型信息移除晰搀,類型信息僅在開發(fā)階段起作用。
全局類型/變量聲明
先在項(xiàng)目 src
目錄中新建 global.d.ts
文件
全局類型聲明
項(xiàng)目的根目錄有 tsconfig.json 可以配置 TypeScript办斑,include 屬性包含了需要校驗(yàn) ts 的文件外恕。ts
默認(rèn)會(huì)將 xx.d.ts
類型文件中的類型注冊(cè)成全局的,下面舉個(gè)栗子:
// global.d.ts
type T1 = number
// 組件內(nèi)
<script lang="ts">
let num1: T1 = 1
</script>
全局變量聲明
有三種方式聲明全局變量乡翅,掛載在瀏覽器的 window 對(duì)象中
- 使用 window
global.d.ts
文件
// 若想不帶window使用userId鳞疲,但需重復(fù)聲明
declare let userId: string
interface Window {
userId: string
}
注:不聲明且不帶window使用開發(fā)模式不會(huì)報(bào)錯(cuò),但打包時(shí)會(huì)報(bào)錯(cuò)
組件文件
window.userId = '1'
console.log(userId)
- 使用
global
配合window
或var
蠕蚜,需加export
尚洽,否則會(huì)打包報(bào)錯(cuò)
// global.d.ts
export {}
declare global {
interface Window {
// 使用foo全局變量時(shí)得帶window,否則打包會(huì)報(bào)錯(cuò)
foo: string
}
var age: number
}
// 組件內(nèi)
globalThis.age = 18
window.foo = '1'
console.log(age, window.foo)
注:加上export后其他聲明會(huì)失效靶累,其他聲明可另起 *.d.ts 文件定義
- 使用var
// global.d.ts
declare var age: number
// 組件內(nèi)
globalThis.age = 18
console.log(age)
每種方式各有利弊腺毫,自行選擇
第三方庫(kù)聲明
第三方庫(kù)需有類型聲明,可自動(dòng)生成或者自己寫挣柬,有興趣可移步潮酒。
Vue3 + TS
vue3新增了composition api的寫法,更接近react的寫法凛忿,下面介紹ts下的vue3寫法和生命周期
setup 語(yǔ)法糖
一個(gè)組件選項(xiàng)澈灼,在組件被創(chuàng)建之前,props
被解析之后執(zhí)行店溢。它是組合式 API 的入口。
<script setup>
是在單文件組件 (SFC) 中使用組合式 API 的編譯時(shí)語(yǔ)法糖委乌。相比于普通的 <script>
語(yǔ)法床牧,它具有更多優(yōu)勢(shì):
- 更少的樣板內(nèi)容,更簡(jiǎn)潔的代碼遭贸。
- 能夠使用純 Typescript 聲明 props 和拋出事件戈咳。
- 更好的運(yùn)行時(shí)性能 (其模板會(huì)被編譯成與其同一作用域的渲染函數(shù),沒(méi)有任何的中間代理)壕吹。
- 更好的 IDE 類型推斷性能 (減少語(yǔ)言服務(wù)器從代碼中抽離類型的工作)著蛙。
使用這個(gè)語(yǔ)法,需要將 setup
attribute 添加到 <script>
代碼塊上:
<script setup lang="ts">
</script>
里面的代碼會(huì)被編譯成組件 setup()
函數(shù)的內(nèi)容耳贬。這意味著與普通的 <script>
只在組件被首次引入的時(shí)候執(zhí)行一次不同踏堡,<script setup>
中的代碼會(huì)在每次組件實(shí)例被創(chuàng)建的時(shí)候執(zhí)行。
setup
函數(shù)在生命周期方面咒劲,它是在 beforeCreate 鉤子之前調(diào)用的顷蟆。
生命周期
選項(xiàng)式 API 的生命周期選項(xiàng)和組合式 API 之間的映射
-
-> 使用beforeCreate
setup()
-
-> 使用created
setup()
-
beforeMount
->onBeforeMount
-
mounted
->onMounted
-
beforeUpdate
->onBeforeUpdate
-
updated
->onUpdated
-
beforeUnmount
->onBeforeUnmount
-
unmounted
->onUnmounted
-
errorCaptured
->onErrorCaptured
-
renderTracked
->onRenderTracked
-
renderTriggered
->onRenderTriggered
-
activated
->onActivated
-
deactivated
->onDeactivated
TIP: 因?yàn)?
setup
是圍繞beforeCreate
和created
生命周期鉤子運(yùn)行的诫隅,所以不需要顯式地定義它們。換句話說(shuō)帐偎,在這些鉤子中編寫的任何代碼都應(yīng)該直接在setup
函數(shù)中編寫逐纬。
響應(yīng)式 ref
接受一個(gè)內(nèi)部值并返回一個(gè)響應(yīng)式且可變的 ref 對(duì)象。ref 對(duì)象僅有一個(gè) .value
property削樊,指向該內(nèi)部值豁生。和從 setup()
函數(shù)中返回值一樣,ref 值在模板中使用的時(shí)候會(huì)自動(dòng)解包漫贞。
可以在調(diào)用 ref
時(shí)傳遞一個(gè)泛型參數(shù)以覆蓋默認(rèn)推斷
import { ref } from "vue";
let str = ref<string>("test");
還可以指定復(fù)雜類型
const foo = ref<string | number>('foo') // foo 的類型:Ref<string | number>
foo.value = 123 // ok!
props/emit
- 僅限類型的 props/emit 聲明
defineProps<{ title: string }>();
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
-
props 設(shè)置默認(rèn)值
有兩種方法設(shè)置默認(rèn)值
-
使用
運(yùn)行時(shí)聲明
運(yùn)行時(shí)聲明 的方式只能設(shè)置參數(shù)類型甸箱、默認(rèn)值、是否必傳绕辖、自定義驗(yàn)證摇肌。報(bào)錯(cuò)為控制臺(tái)warn警告。
若想設(shè)置[ 編輯器報(bào)錯(cuò)仪际、編輯器語(yǔ)法提示 ]則需要使用類型聲明的方式围小。const props = defineProps({ modelValue: { type: Boolean, default: false }, title: { type: String, default: '彈窗提示' }, msg: { type: String, default: '彈窗信息' } })
-
使用類型聲明時(shí)的默認(rèn) props 值
僅限類型的
defineProps
聲明的不足之處在于,它不能給 props 定義默認(rèn)值树碱。需配合withDefaults
編譯器宏解決:
-
interface Props {
title?: string;
msg?: string;
}
withDefaults(defineProps<Props>(), {
title: "提示",
msg: "是否跳轉(zhuǎn)到app?",
});
defineProps肯适、withDefaults 是只在
<script setup>
語(yǔ)法糖中才能使用的編譯器宏。他不需要導(dǎo)入且會(huì)隨著<script setup>
處理過(guò)程一同被編譯掉成榜。
v-model 雙向綁定
vue2
中的 v-model
的使用是通過(guò)傳遞 value
屬性和接收 input
事件實(shí)現(xiàn)框舔,vue3
則換成了 modelValue
屬性,接收的方法是update:modelValue
赎婚。
以下彈窗例子以Page.vue為父組件刘绣,Dialog.vue為子組件,關(guān)鍵代碼如下:
/* Page.vue */
<template>
<Dialog v-model="dialogVisible"></Dialog>
<div class="bottom-btn" @click="onTap">點(diǎn)擊按鈕</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Dialog from "./Dialog.vue";
let dialogVisible = ref<boolean>(false);
function onTap() {
dialogVisible.value = true;
}
<script>
/* Dialog.vue */
<template>
<div class="dialog" v-show="modelValue">
<span class="dialog-content-btn" @click="onConfirm">確定</span>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
interface Props {
modelValue?: boolean;
}
let props = withDefaults(defineProps<Props>(), {
modelValue: false // v-model綁定的屬性值
});
// 傳遞的方法
const emit = defineEmits<{
(e: "update:modelValue", visible: boolean): boolean;
}>();
function onConfirm() {
emit("update:modelValue", false);
}
<script>
遇到的問(wèn)題
做好所有配置后挣输,主要遇到以下兩個(gè)問(wèn)題
vite
打包報(bào)錯(cuò)/告警
"@charset" must be the first rule in the file }@charset "UTF-8";
告警如圖:
原因:使用了scss類庫(kù) sass纬凤,scss編譯的時(shí)候,因?yàn)楸痪幾g的文件里可能有中文導(dǎo)致
解決:在vite.config.js里面撩嚼,加一個(gè)sass的配置停士,把charset關(guān)掉就行了
官網(wǎng)對(duì)css預(yù)處理的api
vite.config.js 中的配置
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
charset: false
}
}
}
})
去除 Typescript
全局變量的 eslint
報(bào)錯(cuò)
1. 使用 var
定義全局變量
var
相關(guān)聲明下會(huì)帶下劃線,并報(bào)錯(cuò)
Unexpected var, use let or const instead.
解決:在 .eslintrc
配置文件中增加規(guī)則
rules: {
// 全局變量允許使用 var
'no-var': 'off',
}
2. 使用 global
定義全局變量
global 相關(guān)聲明下會(huì)帶下劃線完丽,并報(bào)錯(cuò)
Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.
解決:在 global.d.ts
聲明文件中添加一行代碼
export {}
注:新增后會(huì)導(dǎo)致該文件中的其他變量/類型等聲明失效恋技,其他聲明可另起 *.d.ts 文件定義
Vite 為什么更快
Vite 主要通過(guò)以下幾個(gè)方面進(jìn)行優(yōu)化:
- 啟動(dòng)應(yīng)用時(shí)按需提供代碼
- 瀏覽器緩存(協(xié)商緩存和強(qiáng)緩存)進(jìn)行代碼更新
- 使用 esbuild 預(yù)構(gòu)建依賴和加快構(gòu)建速度
啟動(dòng)時(shí)間和更新時(shí)間
啟動(dòng)時(shí)間
以往的打包工具當(dāng)冷啟動(dòng)開發(fā)服務(wù)器時(shí),基于打包器的方式啟動(dòng)必須優(yōu)先抓取并構(gòu)建你的整個(gè)應(yīng)用逻族,然后才能提供服務(wù)蜻底。而且存在性能瓶頸——使用 JavaScript 開發(fā)的工具通常需要很長(zhǎng)時(shí)間(甚至是幾分鐘!)才能啟動(dòng)開發(fā)服務(wù)器瓷耙,即使使用 HMR朱躺,文件修改后的效果也需要幾秒鐘才能在瀏覽器中反映出來(lái)刁赖。
Vite 通過(guò)在一開始將應(yīng)用中的模塊區(qū)分為 依賴 和 源碼 兩類,并只在瀏覽器請(qǐng)求源碼時(shí)進(jìn)行轉(zhuǎn)換并按需提供源碼长搀,改進(jìn)了開發(fā)服務(wù)器啟動(dòng)時(shí)間宇弛。而且esbuild 預(yù)構(gòu)建依賴使用的語(yǔ)言是go,比以 JavaScript 編寫的打包器預(yù)構(gòu)建依賴快 10-100 倍源请。
更新時(shí)間
在 Vite 中枪芒,HMR 是在原生 ESM 上執(zhí)行的。當(dāng)編輯一個(gè)文件時(shí)谁尸,Vite 只需要精確地使已編輯的模塊與其最近的 HMR 邊界之間的鏈?zhǔn)Щ?a target="_blank">[1](大多數(shù)時(shí)候只是模塊本身)舅踪,使得無(wú)論應(yīng)用大小如何,HMR 始終能保持快速更新良蛮。
Vite 同時(shí)利用 HTTP 頭來(lái)加速整個(gè)頁(yè)面的重新加載(再次讓瀏覽器為我們做更多事情):源碼模塊的請(qǐng)求會(huì)根據(jù) 304 Not Modified
進(jìn)行協(xié)商緩存抽碌,而依賴模塊請(qǐng)求則會(huì)通過(guò) Cache-Control: max-age=31536000,immutable
進(jìn)行強(qiáng)緩存,因此一旦被緩存它們將不需要再次請(qǐng)求决瞳。
預(yù)構(gòu)建依賴的前因后果
Vite 預(yù)構(gòu)建依賴原因有二:
CommonJS 和 UMD 兼容性: 開發(fā)階段中货徙,Vite 的開發(fā)服務(wù)器將所有代碼視為原生 ES 模塊。因此皮胡,Vite 必須先將作為 CommonJS 或 UMD 發(fā)布的依賴項(xiàng)轉(zhuǎn)換為 ESM痴颊。
-
性能: Vite 通過(guò)預(yù)構(gòu)建依賴將有許多內(nèi)部模塊的 ESM 依賴關(guān)系轉(zhuǎn)換為單個(gè)模塊,從而減少瀏覽器的請(qǐng)求數(shù)量屡贺,提升頁(yè)面加載性能蠢棱。
如
lodash-es
有超過(guò) 600 個(gè)內(nèi)置模塊,當(dāng)執(zhí)行import { debounce } from 'lodash-es'
時(shí)甩栈,瀏覽器同時(shí)發(fā)出 600 多個(gè) HTTP 請(qǐng)求泻仙;通過(guò)預(yù)構(gòu)建lodash-es
成為一個(gè)模塊,就只需要一個(gè) HTTP 請(qǐng)求量没。
自動(dòng)依賴搜尋
如果沒(méi)有找到相應(yīng)的緩存饰豺,Vite 將抓取你的源碼,并自動(dòng)尋找引入的依賴項(xiàng)(即 "bare import",表示期望從 node_modules
解析)戏仓,并將這些依賴項(xiàng)作為預(yù)構(gòu)建包的入口點(diǎn)崖蜜。
在服務(wù)器已經(jīng)啟動(dòng)之后,如果遇到一個(gè)新的依賴關(guān)系導(dǎo)入祟峦,而這個(gè)依賴關(guān)系還沒(méi)有在緩存中,Vite 將重新運(yùn)行依賴構(gòu)建進(jìn)程并重新加載頁(yè)面。
對(duì)于 monorepo 倉(cāng)庫(kù)中的某個(gè)依賴成為另一個(gè)包的依賴妓蛮,Vite 會(huì)自動(dòng)偵測(cè)沒(méi)有從 node_modules
解析的依賴項(xiàng),并將鏈接的依賴視為源碼圾叼。它不會(huì)嘗試打包被鏈接的依賴蛤克,而是會(huì)分析被鏈接依賴的依賴列表捺癞。
緩存
文件系統(tǒng)緩存
Vite 會(huì)將預(yù)構(gòu)建的依賴緩存到 node_modules/.vite
。它根據(jù)幾個(gè)源來(lái)決定是否需要重新運(yùn)行預(yù)構(gòu)建步驟:
-
package.json
中的dependencies
列表 - 包管理器的 lockfile构挤,例如
package-lock.json
,yarn.lock
髓介,或者pnpm-lock.yaml
- 可能在
vite.config.js
相關(guān)字段中配置過(guò)的
只有在上述其中一項(xiàng)發(fā)生更改時(shí),才需要重新運(yùn)行預(yù)構(gòu)建筋现。
如果要強(qiáng)制 Vite 重新構(gòu)建依賴唐础,你可以用 --force
命令行選項(xiàng)啟動(dòng)開發(fā)服務(wù)器,或者手動(dòng)刪除 node_modules/.vite
目錄矾飞。
瀏覽器緩存
解析后的依賴請(qǐng)求會(huì)以 HTTP 頭 max-age=31536000,immutable
強(qiáng)緩存一膨,以提高在開發(fā)時(shí)的頁(yè)面重載性能。一旦被緩存洒沦,這些請(qǐng)求將永遠(yuǎn)不會(huì)再到達(dá)開發(fā)服務(wù)器豹绪。如果安裝了不同的版本(這反映在包管理器的 lockfile 中),則附加的版本 query 會(huì)自動(dòng)使它們失效申眼。如果你想通過(guò)本地編輯來(lái)調(diào)試依賴項(xiàng)瞒津,你可以:
- 通過(guò)瀏覽器調(diào)試工具的 Network 選項(xiàng)卡暫時(shí)禁用緩存;
- 重啟 Vite dev server豺型,并添加
--force
命令以重新構(gòu)建依賴仲智; - 重新載入頁(yè)面。
為何不用 ESBuild 打包姻氨?
雖然 esbuild
快得驚人钓辆,且是一個(gè)在構(gòu)建庫(kù)方面比較出色的工具,但一些針對(duì)構(gòu)建 應(yīng)用 的重要功能仍然還在持續(xù)開發(fā)中 —— 特別是代碼分割和 CSS 處理方面肴焊。就目前來(lái)說(shuō)前联,Rollup 在應(yīng)用打包方面更加成熟和靈活。
最后
最后附上完整代碼娶眷,如對(duì)前端自動(dòng)化部署有興趣似嗤,可繼續(xù)看在本文 vue3 基礎(chǔ)上搭建的 CICD
相關(guān)文章