戰(zhàn)開發(fā)經(jīng)驗(yàn)總結(jié):從項(xiàng)目搭建到性能優(yōu)化的全套思路)
做了幾年Vue網(wǎng)站開發(fā)從最初拿官方文檔當(dāng)小說(shuō)啃到后來(lái)在真實(shí)項(xiàng)目里被各種邊界情況毒打積累了不少心得。這篇文章不是官方文檔的復(fù)述而是我在實(shí)際項(xiàng)目中總結(jié)出的整套Vue開發(fā)思路從為什么選Vue、項(xiàng)目怎么搭才順手、組件到底怎么拆到響應(yīng)式原理和性能排查最后再把新手最容易踩的坑一次性列出來(lái)。適合剛?cè)腴T想建立整體認(rèn)知的人也適合已經(jīng)寫了一陣子Vue但總覺(jué)得差點(diǎn)意思的開發(fā)者。很多人學(xué)Vue是直接從組件寫起跟著教程復(fù)制一個(gè)to-do list跑通了就覺(jué)得自己會(huì)了。但等接手真實(shí)項(xiàng)目遇到數(shù)據(jù)改了視圖不變、組件瘋狂重渲染、路由權(quán)限繞來(lái)繞去才發(fā)現(xiàn)自己只是知道語(yǔ)法不懂框架的脾氣。我希望這篇文章能幫你在動(dòng)手之前先建立一套相對(duì)清晰的心智模型再帶著模型去寫代碼會(huì)順手很多。1. 為什么選Vue框架選型背后的實(shí)際考量1.1 漸進(jìn)式設(shè)計(jì)從“一個(gè)頁(yè)面”到“一套系統(tǒng)”的平滑過(guò)渡Vue最打動(dòng)我的地方不是某個(gè)炫技特性而是它的漸進(jìn)式思路。你可以只在頁(yè)面里引入一個(gè)全局腳本把某個(gè)表單交互做掉也可以配上路由、狀態(tài)管理、服務(wù)端渲染做成一個(gè)完整的中后臺(tái)系統(tǒng)。這個(gè)“按需取用”的特性在實(shí)際項(xiàng)目中非常實(shí)用。很多團(tuán)隊(duì)不是從零開始選型而是接手一個(gè)老項(xiàng)目想在保留現(xiàn)有架構(gòu)的前提下逐步引入新框架。Vue的這種設(shè)計(jì)讓遷移成本低很多先在一個(gè)角落用起來(lái)驗(yàn)證效果后再逐步擴(kuò)大范圍。比如我在一個(gè)舊項(xiàng)目中引入Vue最初只是用它渲染一個(gè)動(dòng)態(tài)表格后來(lái)才慢慢把篩選、彈窗、表單校驗(yàn)都遷進(jìn)來(lái)。整個(gè)過(guò)程沒(méi)有“推倒重來(lái)”的陣痛業(yè)務(wù)連續(xù)性保住了技術(shù)債也還得起。對(duì)比一下React它的函數(shù)式理念更純粹但初始學(xué)習(xí)曲線會(huì)陡一些JSX、不可變數(shù)據(jù)、Hooks規(guī)則剛接觸時(shí)很容易寫出“看起來(lái)能跑但思路混亂”的代碼。Angular則更像一套全家桶約束多、概念多、依賴注入、RxJS適合大團(tuán)隊(duì)統(tǒng)一規(guī)范但對(duì)小團(tuán)隊(duì)和個(gè)人項(xiàng)目來(lái)說(shuō)光是想清楚模塊劃分就要折騰很久。Vue的好處是它不強(qiáng)求你一次性接受所有概念你可以先寫模板再接觸選項(xiàng)式API最后才上組合式API和TypeScript每一步都有一個(gè)自然的“剛好夠用”的層次。1.2 響應(yīng)式數(shù)據(jù)模型上手快、心智負(fù)擔(dān)低的根本原因Vue的響應(yīng)式系統(tǒng)讓人感覺(jué)“數(shù)據(jù)改了視圖就自動(dòng)更新”這個(gè)體驗(yàn)在小項(xiàng)目里特別爽。你不需要像用原生DOM那樣手動(dòng)操作節(jié)點(diǎn)也不用像早年的jQuery那樣對(duì)一堆DOM事件來(lái)回綁定。模板和數(shù)據(jù)之間的關(guān)聯(lián)是聲明式的代碼量直接少了一大截。舉個(gè)例子做一個(gè)篩選聯(lián)動(dòng)選完省份城市下拉框要跟著刷新。用原生寫法你需要監(jiān)聽change事件然后查DOM、清空選項(xiàng)、構(gòu)造新選項(xiàng)、再綁定事件。用Vue你只需要維護(hù)一個(gè)cities數(shù)組省份一變計(jì)算屬性或偵聽器就會(huì)重新計(jì)算模板自動(dòng)重新渲染??雌饋?lái)是小事但整個(gè)項(xiàng)目里到處都是這種“小事”積累起來(lái)就是把開發(fā)周期砍半的效力。當(dāng)然響應(yīng)式不是魔法。Vue 2用的是Object.defineProperty只能攔截對(duì)象屬性的讀取和賦值所以新增屬性、數(shù)組下標(biāo)修改這些操作會(huì)失效。Vue 3改用Proxy可以代理整個(gè)對(duì)象所以動(dòng)態(tài)增刪屬性、監(jiān)聽數(shù)組變化都比Vue 2更徹底。這個(gè)區(qū)別在你調(diào)試“數(shù)據(jù)明明改了視圖卻沒(méi)反應(yīng)”這種問(wèn)題時(shí)特別關(guān)鍵。很多時(shí)候不是框架bug而是你還在用Vue 2時(shí)代的寫法操作Vue 3的數(shù)據(jù)自然會(huì)踩坑。1.3 什么時(shí)候不要選Vue雖然我對(duì)Vue有偏好但必須承認(rèn)它不適用于所有場(chǎng)景。如果你的項(xiàng)目是一個(gè)超大型、需要高度類型推導(dǎo)和嚴(yán)格架構(gòu)約束的團(tuán)隊(duì)項(xiàng)目并且團(tuán)隊(duì)里已經(jīng)有React或Angular的資深專家那沒(méi)必要強(qiáng)行換Vue。Vue的靈活是一把雙刃劍靈活意味著可選方案多團(tuán)隊(duì)如果沒(méi)有統(tǒng)一規(guī)范不同人寫出來(lái)的代碼風(fēng)格可能差異很大。另外如果你的核心業(yè)務(wù)高度依賴原生WebGL、復(fù)雜動(dòng)畫編排或者需要直接嵌入一些非Vue生態(tài)的庫(kù)Vue提供的那層抽象反而可能成為阻力。這時(shí)候不如直接用原生或更底層的框架。選型這事兒沒(méi)有“最好的框架”只有“最適合當(dāng)前團(tuán)隊(duì)和業(yè)務(wù)的框架”。我的經(jīng)驗(yàn)是中小型團(tuán)隊(duì)、業(yè)務(wù)迭代快、需要快速出活Vue幾乎是性價(jià)比很高的選擇。2. 項(xiàng)目搭建與工程化實(shí)踐從零到一的關(guān)鍵選擇2.1 腳手架我用Vite替代了Vue CLI之后回不去了新項(xiàng)目啟動(dòng)過(guò)去官方推薦vue-cli底層是webpack配起來(lái)繁瑣啟動(dòng)慢改個(gè)配置要翻半天文檔?,F(xiàn)在我用Vite啟動(dòng)速度真的是秒開。Vite開發(fā)環(huán)境用原生ES Module加載不用像webpack那樣把所有模塊預(yù)先打包成bundle所以冷啟動(dòng)快熱更新也快改動(dòng)代碼后刷新幾乎是即時(shí)的。我第一次用Vite搭項(xiàng)目時(shí)還有點(diǎn)不放心擔(dān)心插件生態(tài)跟不上。實(shí)際用了才發(fā)現(xiàn)主流工具基本都有Vite插件版本Vue、Router、Pinia、ESLint、Prettier、UnoCSS全都能在Vite里跑得很順暢。而且Vite內(nèi)置開發(fā)服務(wù)器和構(gòu)建優(yōu)化生產(chǎn)環(huán)境用Rollup打包產(chǎn)物體積控制和Tree Shaking做得也不錯(cuò)。但Vite也不是完全沒(méi)有坑。它要求Node版本比較新舊項(xiàng)目如果鎖定在老版本Node上升級(jí)成本需要考慮。另外有些依賴包只提供CommonJS版本在Vite里需要配置optimizeDeps之類的參數(shù)才能正常工作。新手遇到這類問(wèn)題容易懵其實(shí)就是專門處理一下依賴預(yù)構(gòu)建的兼容性就好。提示如果你現(xiàn)在要開新項(xiàng)目直接用Vite Vue 3不要再走Vue CLI的老路了。老項(xiàng)目用什么維持什么新項(xiàng)目就別給自己添堵。2.2 目錄結(jié)構(gòu)照著這個(gè)分層維護(hù)半年也不亂項(xiàng)目搭起來(lái)了目錄結(jié)構(gòu)決定了后面代碼能清晰多久。我踩過(guò)“把所有組件都塞到components里頁(yè)面與頁(yè)面之間邏輯互相糾纏”的坑后來(lái)慢慢總結(jié)出一套適合中后臺(tái)項(xiàng)目的分層方式src/api所有后端接口請(qǐng)求按業(yè)務(wù)模塊拆文件src/assets靜態(tài)資源圖片、字體、全局樣式src/components通用組件和具體業(yè)務(wù)無(wú)關(guān)src/composables組合式函數(shù)封裝可復(fù)用的邏輯src/layouts頁(yè)面布局組件比如側(cè)邊欄頂欄的框架src/router路由配置與守衛(wèi)src/storesPinia狀態(tài)管理按模塊拆分src/views頁(yè)面級(jí)組件一個(gè)路由對(duì)應(yīng)一個(gè)src/utils工具函數(shù)純邏輯不依賴Vuesrc/typesTypeScript類型定義這樣分層最大的好處是“看路徑就知道這個(gè)文件是干什么的”。你不需要翻遍代碼才能找到接口封裝也不需要在一個(gè)巨型組件里找業(yè)務(wù)邏輯。尤其是在團(tuán)隊(duì)協(xié)作的時(shí)候新人接手項(xiàng)目能快速定位問(wèn)題域。我見(jiàn)過(guò)有人把Api請(qǐng)求直接寫在組件里當(dāng)時(shí)覺(jué)得省事后來(lái)接口字段變了要全局搜索好幾個(gè)組件逐一修改非常痛苦。統(tǒng)一放到api目錄后改動(dòng)一個(gè)文件就好組件的職責(zé)也純粹了。2.3 狀態(tài)管理選型Pinia好在哪里Vue 3時(shí)代狀態(tài)管理我直接選Pinia基本不用猶豫。Pinia的設(shè)計(jì)比Vuex簡(jiǎn)潔太多去掉了mutations的概念直接改state配合Composition Api的寫法代碼體量小很多而且天然支持TypeScript。舉個(gè)例子一個(gè)用戶登錄狀態(tài)的管理Vuex需要定義state、mutations、actions、getters四件套還要考慮模塊命名空間寫起來(lái)很儀式感。Pinia直接定義一個(gè)useUserStore內(nèi)部放state和action組件里調(diào)用store的方法就直接改數(shù)據(jù)清爽得很。Pinia還解決了Vuex的一個(gè)長(zhǎng)期痛點(diǎn)模塊嵌套太深時(shí)mapState和mapActions寫起來(lái)繞。Pinia里每個(gè)store都是獨(dú)立的通過(guò)storeToRefs之類的輔助函數(shù)可以很方便地解構(gòu)使用心智負(fù)擔(dān)低多了。不過(guò)狀態(tài)管理也不是用得越多越好。我的原則是只在多個(gè)不相關(guān)的組件需要共享同一份數(shù)據(jù)時(shí)才用store比如用戶信息、權(quán)限、購(gòu)物車、全局配置。如果只是父子組件傳值那就用props和emit不要把所有東西都塞到store里否則store變成一個(gè)大垃圾箱調(diào)試起來(lái)很痛苦。2.4 環(huán)境變量與接口封裝最容易翻車的基礎(chǔ)建設(shè)環(huán)境變量這塊Vite默認(rèn)只有import.meta.env你需要把自定義變量放到.env.development和.env.production里變量名前綴必須是VITE_才暴露給前端。比如VITE_API_BASE_URL/api代碼里用import.meta.env.VITE_API_BASE_URL訪問(wèn)。接口封裝我用axios作為基礎(chǔ)網(wǎng)絡(luò)庫(kù)然后在api目錄里做一個(gè)統(tǒng)一實(shí)例配置好請(qǐng)求頭、超時(shí)時(shí)間、攔截器。攔截器里做兩件最重要的事請(qǐng)求攔截器統(tǒng)一攜帶token響應(yīng)攔截器統(tǒng)一處理HTTP錯(cuò)誤碼和業(yè)務(wù)錯(cuò)誤碼比如401跳登錄、500提示服務(wù)器異常。這樣每個(gè)業(yè)務(wù)模塊的接口函數(shù)只需要負(fù)責(zé)拼接地址和傳參不需要重復(fù)寫錯(cuò)誤處理邏輯。這里有一個(gè)容易踩的坑后端返回的錯(cuò)誤信息結(jié)構(gòu)不統(tǒng)一。有的接口返回{ success: false, msg: xx }有的返回{ code: 1, message: xx }如果你在攔截器里硬編碼了某一種結(jié)構(gòu)遇到另一個(gè)接口就掛了。最好是和后端提前約定好統(tǒng)一的返回格式或者在攔截器里做兼容處理。這個(gè)看似基礎(chǔ)但實(shí)際項(xiàng)目中幾乎每個(gè)團(tuán)隊(duì)都要在這上面消耗不少時(shí)間。3. 組件化開發(fā)組件不是“越長(zhǎng)越好”而是“越清晰越好”3.1 組件設(shè)計(jì)的三條邊界責(zé)任、可復(fù)用性、通信成本寫組件最容易出現(xiàn)的錯(cuò)誤就是“一個(gè)組件負(fù)責(zé)太多事情”。我今天還看到過(guò)有人把表格、表單、彈窗、分頁(yè)全部塞進(jìn)一個(gè)BigThing.vue四千多行代碼看著就跑不動(dòng)。組件拆分的邊界我認(rèn)為可以從三個(gè)維度考慮第一是責(zé)任邊界一個(gè)組件只做一件事。表格組件就負(fù)責(zé)渲染和操作行數(shù)據(jù)彈窗組件只負(fù)責(zé)顯示和關(guān)閉業(yè)務(wù)邏輯放到外層頁(yè)面或?qū)iT的composable里。第二是可復(fù)用性邊界你可以問(wèn)自己“這個(gè)組件在另一個(gè)頁(yè)面能不能用”如果答案是不能可能它不應(yīng)該叫通用組件而應(yīng)該放在業(yè)務(wù)模塊里。第三是通信成本邊界組件嵌套層級(jí)太深props和emit反復(fù)中轉(zhuǎn)至少超過(guò)三層就考慮用插槽或者放到store里別為了“完全解耦”把組件之間的通信搞得比直接改store還費(fèi)勁。3.2 組合式API我更推薦組件的邏輯以“功能”聚合Vue 2時(shí)代寫選項(xiàng)式API把一個(gè)功能的邏輯拆成data、computed、methods、watch幾部分一個(gè)稍微復(fù)雜的組件里代碼就像在幾個(gè)區(qū)域之間來(lái)回蹦跶。組合式API最大的改變是允許你按“功能”去組織代碼而不是按“選項(xiàng)類型”去組織。我后來(lái)寫組件的基本習(xí)慣是把邏輯先用script setup里的一組相關(guān)變量、函數(shù)、computed、watch放一起形成一個(gè)一個(gè)的“邏輯塊”。比如一個(gè)訂單列表組件里面有加載訂單、篩選訂單、排序、分頁(yè)我就會(huì)把跟訂單加載相關(guān)的邏輯放在一起跟篩選相關(guān)的放在一起跟分頁(yè)相關(guān)的放在一起。這樣看代碼的時(shí)候一個(gè)功能的所有相關(guān)代碼都在一個(gè)區(qū)域里維護(hù)起來(lái)非常順。組合式函數(shù)可以繼續(xù)抽離重復(fù)邏輯。比如“表格加載數(shù)據(jù)”這個(gè)動(dòng)作很多頁(yè)面都有那我就可以寫一個(gè)useTable組合式函數(shù)把loading、data列表、分頁(yè)參數(shù)、刷新方法都封裝進(jìn)去組件里直接調(diào)用就行。這個(gè)路子我實(shí)踐下來(lái)確實(shí)能顯著減少重復(fù)代碼也讓組件看起來(lái)特別干凈。3.3 Props、事件、插槽組件之間的溝通語(yǔ)言Props和事件是父子組件通信的基本方式。Props要寫得克制只傳你需要的不要傳一個(gè)整對(duì)象進(jìn)去然后又用一堆watch去拆它這樣容易讓Vue追蹤不到依賴。事件命名建議統(tǒng)一風(fēng)格我習(xí)慣用on開頭比如on-search、on-close這樣語(yǔ)義清楚別人接手時(shí)一看就知道這個(gè)組件能做什么。插槽是Vue一個(gè)很容易被低估的特性。當(dāng)組件內(nèi)部布局有變化時(shí)props和事件很難滿足。比如一個(gè)通用卡片組件頭部、內(nèi)容、底部在業(yè)務(wù)里都有自定義需求如果你用props去傳一堆HTML模板字符串又齷齪又難維護(hù)。正確的做法是用具名插槽template div classcard header v-if$slots.header slot nameheader / /header main slot / /main footer v-if$slots.footer slot namefooter / /footer /div /template這樣卡片的框架是固定的內(nèi)容區(qū)域完全交給使用方自己填充。插槽讓組件的擴(kuò)展能力上了一個(gè)臺(tái)階。3.4 組件通信的“潛規(guī)則”什么時(shí)候不要用props組件通信方式包括props、事件、v-model、provide/inject、Pinia。很多人喜歡一股腦用props傳遞所有數(shù)據(jù)結(jié)果組件層級(jí)一深中間層全都是無(wú)意義的轉(zhuǎn)發(fā)。我的經(jīng)驗(yàn)是對(duì)于深層嵌套的跨層級(jí)傳遞用provide/inject要省事很多它可以隔代傳數(shù)據(jù)但有一點(diǎn)需要留意——它會(huì)讓組件之間的依賴關(guān)系變得“隱形”不利于代碼追蹤。所以provide/inject適合傳遞類似主題配置、用戶信息、權(quán)限標(biāo)識(shí)這樣相對(duì)穩(wěn)定的全局?jǐn)?shù)據(jù)業(yè)務(wù)狀態(tài)盡量還是放Pinia。另外v-model不只是表單控件可以用自定義組件也可以。比如一個(gè)自定義的日期范圍選擇組件你想要讓父組件能通過(guò)v-model綁定一個(gè)區(qū)間數(shù)組就在組件里設(shè)置modelValue和update:modelValue事件。這個(gè)模式很適合做“受控組件”父組件負(fù)責(zé)保存數(shù)據(jù)子組件負(fù)責(zé)交互數(shù)據(jù)流清晰調(diào)試也方便。4. 響應(yīng)式原理與性能優(yōu)化知其所以然才能寫得快4.1 響應(yīng)式系統(tǒng)如何工作從getter到依賴收集Vue 3的響應(yīng)式底層是reactive和effect。簡(jiǎn)單說(shuō)當(dāng)你讀取一個(gè)響應(yīng)式對(duì)象的屬性時(shí)當(dāng)前正在執(zhí)行的副作用函數(shù)會(huì)被記為“依賴”當(dāng)你修改這個(gè)屬性時(shí)Vue會(huì)觸發(fā)它收集到的依賴函數(shù)重新執(zhí)行。這個(gè)機(jī)制叫“依賴收集”。我當(dāng)初學(xué)這塊的時(shí)候總感覺(jué)抽象后來(lái)我自己畫了個(gè)比喻把響應(yīng)式系統(tǒng)想象成一個(gè)社交圈子。數(shù)據(jù)是明星頁(yè)面是粉絲。數(shù)據(jù)在讀取的時(shí)候粉絲就開始關(guān)注數(shù)據(jù)依賴收集。數(shù)據(jù)一變明星發(fā)條動(dòng)態(tài)所有關(guān)注它的粉絲都會(huì)刷到消息觸發(fā)更新。這個(gè)刷動(dòng)態(tài)的過(guò)程就是組件重新渲染。知道原理后很多問(wèn)題就迎刃而解。比如為什么在Vue 3里reactive包裹的對(duì)象解構(gòu)之后會(huì)失去響應(yīng)性因?yàn)榻鈽?gòu)出來(lái)的普通變量只是讀取了一次值并沒(méi)有在副作用函數(shù)里持續(xù)追蹤它。明白了這個(gè)點(diǎn)你就知道應(yīng)該使用toRefs或ref來(lái)保持響應(yīng)性。4.2 別急著優(yōu)化先找到“真瓶頸”很多剛接觸性能調(diào)優(yōu)的新手一上來(lái)就想著用memo、shallowRef、v-memo這些花哨手段其實(shí)大多數(shù)項(xiàng)目根本不需要。Vue的響應(yīng)式更新已經(jīng)足夠快真正的瓶頸往往出在三種地方一是渲染了超大列表比如幾千行的表格二是頻繁觸發(fā)高耗時(shí)計(jì)算比如復(fù)雜的篩選、排序三是組件更新范圍太大比如修改一個(gè)全局狀態(tài)導(dǎo)致所有組件重新渲染。性能優(yōu)化首先要量化。打開瀏覽器開發(fā)者工具看Performance面板記錄用戶交互前后的長(zhǎng)任務(wù)和渲染時(shí)間。如果一個(gè)交互導(dǎo)致主線程忙了300毫秒以上那才是需要優(yōu)化的情況。我見(jiàn)過(guò)有人為了一個(gè)只需要渲染50條數(shù)據(jù)的列表搞了一堆virtual-list優(yōu)化結(jié)果復(fù)雜度上來(lái)了收益卻幾乎沒(méi)有。過(guò)度優(yōu)化是現(xiàn)代前端的一種病得克制。4.3 值得用的性能優(yōu)化手段computed、v-for key、事件委托真正值得默認(rèn)使用的優(yōu)化手段其實(shí)很簡(jiǎn)單computed能緩存的盡量用computed避免模板里寫復(fù)雜表達(dá)式。計(jì)算屬性只有相關(guān)依賴變化時(shí)才重新計(jì)算這點(diǎn)和method不同。我經(jīng)常在模板里看到有人寫list.filter().map()每次渲染都全量執(zhí)行一遍改成computed之后好很多。v-for一定要給key而且key最好是業(yè)務(wù)里的唯一ID不要用index。用index作key在列表插入、刪除、排序時(shí)會(huì)出各種詭異問(wèn)題輕則性能浪費(fèi)重則狀態(tài)錯(cuò)亂。大量同類事件的監(jiān)聽用事件委托。比如一個(gè)表格里每一行都有刪除按鈕如果給每個(gè)按鈕都綁一個(gè)監(jiān)聽列表大了會(huì)有不必要的內(nèi)存開銷。我把監(jiān)聽綁在表格容器上通過(guò)事件參數(shù)判斷來(lái)源性能提升明顯。4.4 異步更新與nextTickVue不是“實(shí)時(shí)渲染”Vue的數(shù)據(jù)更新和DOM渲染是異步的。你修改一個(gè)響應(yīng)式數(shù)據(jù)后DOM不會(huì)立刻變化而是要等到Vue的更新隊(duì)列flush。這是為了合并同一事件循環(huán)里的多次修改避免重復(fù)渲染。所以當(dāng)你需要在數(shù)據(jù)修改之后立刻讀取DOM位置或尺寸時(shí)就必須用nextTickconst count ref(0) function handleClick() { count.value // 這里DOM還沒(méi)有更新 nextTick(() { // 這里DOM已經(jīng)更新了 }) }我踩過(guò)類似坑修改列表數(shù)據(jù)后馬上要把滾動(dòng)條滾到底部直接用scrollTop scrollHeight結(jié)果還是舊列表高度。用nextTick包一下就好了。5. 實(shí)戰(zhàn)中的常見(jiàn)問(wèn)題與排查技巧5.1 生命周期與異步請(qǐng)求為什么數(shù)據(jù)在頁(yè)面里是空的新手經(jīng)常遇到一個(gè)問(wèn)題進(jìn)入詳情頁(yè)接口數(shù)據(jù)還沒(méi)返回組件已經(jīng)開始渲染了結(jié)果頁(yè)面上先是空數(shù)據(jù)一閃而過(guò)或者直接報(bào)錯(cuò)“讀取不到undefined的某個(gè)屬性”。解決辦法其實(shí)不是推遲請(qǐng)求而是正確處理異步落地。我通常會(huì)在請(qǐng)求結(jié)果返回之后把loading置為false模板里用v-if或v-loading控制渲染時(shí)機(jī)template div v-ifloading加載中.../div div v-else-iferror{{ error }}/div div v-else !-- 正常內(nèi)容 -- /div /template另外注意在組件卸載時(shí)取消未完成的請(qǐng)求避免已經(jīng)離開頁(yè)面了還在setStateVue里是直接改響應(yīng)式變量這叫“內(nèi)存泄漏”。用axios的話可以通過(guò)AbortController或axios的CancelToken來(lái)取消。5.2 路由守衛(wèi)與權(quán)限控制別把所有邏輯都堆在全局路由里中后臺(tái)系統(tǒng)的權(quán)限控制是必考題。正常的流程是用戶在登錄頁(yè)輸入賬號(hào)密碼拿到token然后路由跳轉(zhuǎn)時(shí)通過(guò)全局守衛(wèi)校驗(yàn)token是否存在同時(shí)拉取用戶信息和權(quán)限列表動(dòng)態(tài)生成可訪問(wèn)的路由表。很多新手把權(quán)限校驗(yàn)直接寫在全局beforeEach里代碼越來(lái)越長(zhǎng)各種特殊情況用if-else嵌套最終沒(méi)法維護(hù)。我的經(jīng)驗(yàn)是拆開路由先分成“公開路由”和“權(quán)限路由”兩類權(quán)限路由用一個(gè)常量數(shù)組定義在登錄后根據(jù)用戶權(quán)限過(guò)濾一遍再動(dòng)態(tài)注冊(cè)全局守衛(wèi)只負(fù)責(zé)三件事有沒(méi)有token、token是否過(guò)期、當(dāng)前訪問(wèn)的路由是否在白名單內(nèi)動(dòng)態(tài)路由注冊(cè)用router.addRoute每次登錄后重新構(gòu)建。注銷時(shí)別忘了重置路由表否則重新登錄時(shí)權(quán)限變了路由還留著之前的就出嚴(yán)重bug。5.3 表格和列表渲染的性能問(wèn)題監(jiān)控、虛擬滾動(dòng)、分頁(yè)后臺(tái)項(xiàng)目里最吃性能的就是大數(shù)據(jù)表格。幾千條數(shù)據(jù)渲染成表格如果不做任何處理每次輸入篩選都卡體驗(yàn)極差。我的經(jīng)驗(yàn)是分級(jí)處理如果數(shù)據(jù)量在幾百條以內(nèi)普通渲染加computed篩選就夠了不要上復(fù)雜方案如果數(shù)據(jù)量超過(guò)一千條考慮服務(wù)端分頁(yè)一次只返回當(dāng)前頁(yè)數(shù)據(jù)如果必須一次性渲染大量數(shù)據(jù)那就用虛擬滾動(dòng)只渲染可視區(qū)內(nèi)的行比如virtual-list相關(guān)組件。但虛擬滾動(dòng)有一些細(xì)節(jié)要注意比如行高必須確定、滾動(dòng)容器和滾動(dòng)條要處理好否則會(huì)出現(xiàn)滾動(dòng)跳位的問(wèn)題另外篩選條件變化時(shí)盡量用防抖函數(shù)控制接口請(qǐng)求頻率免得每敲一個(gè)字符就發(fā)一次請(qǐng)求把后端打爆。這個(gè)點(diǎn)我經(jīng)常用就是用lodash-es的debounce或者自己寫一個(gè)簡(jiǎn)單的定時(shí)器。5.4 常見(jiàn)報(bào)錯(cuò)與解決速查報(bào)錯(cuò)信息常見(jiàn)原因解決辦法Maximum recursive updates exceeded遞歸更新比如在模板里無(wú)限修改響應(yīng)式數(shù)據(jù)檢查computed是否有副作用避免修改依賴項(xiàng)本身Cannot read properties of undefined異步數(shù)據(jù)還沒(méi)到就訪問(wèn)深層屬性用可選鏈?.或提前判斷[Vue warn] Property xx was accessed during render but is not defined模板里引用了未定義的變量或ref未解包檢查ref是否.value或檢查import導(dǎo)入Hydration node mismatchSSR或靜態(tài)托管時(shí)客戶端和服務(wù)端渲染結(jié)果不一致檢查日期、隨機(jī)數(shù)等動(dòng)態(tài)內(nèi)容渲染Failed to resolve component: xx組件沒(méi)有正確注冊(cè)或拼寫錯(cuò)誤檢查導(dǎo)入路徑和組件名5.5 調(diào)試技巧把console.log換成更聰明的寫法調(diào)試Vue組件時(shí)與其到處打console.log看數(shù)據(jù)不如用Vue官方的Devtools瀏覽器擴(kuò)展。它能直觀顯示組件樹、props、事件、Pinia狀態(tài)、路由歷史還可以時(shí)間旅行調(diào)試。我建議把第三方調(diào)試工具和瀏覽器內(nèi)置調(diào)試器結(jié)合使用。如果是要臨時(shí)查看某個(gè)響應(yīng)式變量的變化可以寫一個(gè)watch把變化輸出到控制臺(tái)watch(count, (newVal, oldVal) { console.log(count changed:, oldVal, -, newVal) })這樣調(diào)試信息帶上了前后值比在事件處理函數(shù)里打日志要直觀得多。6. 給新手的建議我的Vue學(xué)習(xí)路徑和避坑經(jīng)驗(yàn)6.1 不要一上來(lái)就全盤學(xué)挑最常用的部分先上手我見(jiàn)過(guò)很多新人從“環(huán)境搭建”開始學(xué)然后卡在webpack配置文件里出不來(lái)最后連一個(gè)組件都沒(méi)寫就放棄了。正確的姿勢(shì)應(yīng)該是先不用腳手架直接在script標(biāo)簽里引入Vue的CDN寫一個(gè)簡(jiǎn)單頁(yè)面體驗(yàn)一下模板語(yǔ)法、事件、數(shù)據(jù)綁定。等有感覺(jué)了再上Vite和完整工程。學(xué)習(xí)順序我從自己的經(jīng)歷出發(fā)建議是模板語(yǔ)法插值、指令、事件綁定響應(yīng)式數(shù)據(jù)ref和reactive、computed、watch組件基礎(chǔ)props、emit、插槽路由基礎(chǔ)創(chuàng)建路由、跳轉(zhuǎn)、傳參、守衛(wèi)狀態(tài)管理Pinia的基本用法工程化Vite配置、環(huán)境變量、構(gòu)建部署深入響應(yīng)式原理和性能優(yōu)化這個(gè)順序基本符合“會(huì)用 - 懂原理 - 能調(diào)優(yōu)”的節(jié)奏。不要跳級(jí)尤其不要一上來(lái)就研究響應(yīng)式源碼那會(huì)勸退。6.2 實(shí)戰(zhàn)項(xiàng)目比視頻教程更重要看視頻和讀文檔是“輸入”自己寫項(xiàng)目才是“輸出”。我學(xué)Vue進(jìn)步最快的一段時(shí)間是因?yàn)橐o團(tuán)隊(duì)搭一個(gè)模擬業(yè)務(wù)的管理后臺(tái)。雖然一開始磕磕絆絆但那個(gè)項(xiàng)目逼著我去處理路由鑒權(quán)、表格復(fù)雜交互、權(quán)限控制、遠(yuǎn)程部署這些問(wèn)題這些全是視頻里不會(huì)演示的細(xì)節(jié)。找項(xiàng)目練手也講究策略。最好是找一個(gè)你自己真正用得上的小工具比如個(gè)人記賬本、團(tuán)隊(duì)會(huì)議預(yù)約系統(tǒng)、簡(jiǎn)單的CMS。照著別人的demo敲代碼效果有限因?yàn)閏lone一個(gè)倉(cāng)庫(kù)改兩個(gè)按鈕不等于會(huì)開發(fā)。你必須從一張白紙開始決定組件怎么拆、數(shù)據(jù)放哪里、路由怎么設(shè)計(jì)這個(gè)過(guò)程才是真正的能力積累。6.3 最后分享一個(gè)我個(gè)人的習(xí)慣開發(fā)Vue項(xiàng)目時(shí)我習(xí)慣在寫每個(gè)功能之前先在紙上或者空白編輯器里列出這個(gè)功能涉及的“數(shù)據(jù)、動(dòng)作、依賴”三張清單。數(shù)據(jù)是頁(yè)面要展示什么動(dòng)作是用戶操作會(huì)觸發(fā)哪些函數(shù)依賴是這個(gè)數(shù)據(jù)需要從哪里來(lái)、影響到哪些其他數(shù)據(jù)。列完清單再開始寫代碼。這個(gè)方法幫我減少了很多次“寫到一半發(fā)現(xiàn)設(shè)計(jì)錯(cuò)了要重來(lái)”的情況。Vue本身是一個(gè)包容性很強(qiáng)的框架上限和下限差距極大。寫一個(gè)小頁(yè)面誰(shuí)都會(huì)但是把幾千行代碼的復(fù)雜系統(tǒng)整理得井井有條才是真正考驗(yàn)水平的地方。希望這篇文章里那些從實(shí)戰(zhàn)里沉淀下來(lái)的思路能讓你在寫Vue時(shí)少走一些彎路。