式開發(fā)入門)
提到Vue現(xiàn)在很多人第一反應(yīng)就是腳手架、Webpack/Vite那一套。但我今天想聊的是另一種打開方式——不使用Webpack的Vue傳統(tǒng)前端使用方式。也就是直接把Vue當(dāng)做一個普通的JS庫來用在HTML里用script標(biāo)簽引進來就開干。這種玩法在如今構(gòu)建工具滿天飛的環(huán)境下聽起來確實有點“復(fù)古”但它真的不是過時而是很多場景下的最優(yōu)解。剛接觸Vue的朋友被各種工具鏈繞得頭暈眼花配置環(huán)境的時間比寫代碼還長。而有經(jīng)驗的開發(fā)者也知道不是所有項目都需要一套完整工程化體系。這篇我先講最基礎(chǔ)的部分怎么引入Vue、怎么創(chuàng)建一個最簡單的實例、怎么寫模板語法、怎么處理事件和樣式。這些是整個系列的地基也是后續(xù)深入組合式API、組件化開發(fā)的前提。把基礎(chǔ)打牢你后面看任何Vue代碼都不會發(fā)怵。1. 緣起為什么還要聊一個“老掉牙”的話題1.1 現(xiàn)代構(gòu)建流程的復(fù)雜度問題這幾年前端開發(fā)的“工程化”確實成熟了但也把不少初學(xué)者擋在了門外。記得我最早接觸Vue時按教程走第一步就是裝Node.js然后全局安裝腳手架再執(zhí)行創(chuàng)建命令。中間遇到權(quán)限問題、網(wǎng)絡(luò)超時、版本沖突光環(huán)境就折騰了兩三天真正寫Vue代碼的時間幾乎為零。那種挫敗感很多人應(yīng)該體會過。而且構(gòu)建工具本身也在不斷演進。今天你用Webpack配置了一大堆東西明天可能社區(qū)就推薦Vite了。但無論工具怎么變Vue的核心概念——響應(yīng)式數(shù)據(jù)、模板指令、組件這些從來沒變過。不通過構(gòu)建工具直接在瀏覽器里跑Vue恰恰能讓這些核心概念以最直白的方式呈現(xiàn)出來。說白了工具是為解決問題服務(wù)的而不是反過來。如果只是做一個活動頁、一個課程表小程序、一個后臺系統(tǒng)的局部交互模塊非要上一套完整的工程化體系反而是本末倒置。1.2 傳統(tǒng)引入方式的適用場景那什么樣的情況適合這種傳統(tǒng)方式我盤點了一下大概有這么幾類公司老項目里要局部引入Vue做交互比如給原有的服務(wù)端模板頁面加上實時篩選、動態(tài)表格。個人練手做小demo驗證一個想法或者寫點學(xué)習(xí)筆記。后端同學(xué)一個人想搞定一個帶簡單交互的內(nèi)部工具不想折騰node環(huán)境。某些部署環(huán)境限制多不能隨意跑構(gòu)建命令但可以直接放靜態(tài)文件。其實還有一個很實際的原因加載速度快。CDN上的Vue文件本身就是壓縮好的直接引入沒有任何打包和轉(zhuǎn)換過程。對于簡單頁面來說首屏性能可能比你引一堆按需加載的模塊還要好。這個系列我打算用幾篇文章把這種傳統(tǒng)Vue開發(fā)方式從入門到進階完整過一遍。作為第一篇先把地基打好。補充一句本文的示例代碼都能直接在瀏覽器里跑不需要任何命令行操作。你只需要準(zhǔn)備一個文本編輯器和一個瀏覽器就夠了。2. 環(huán)境準(zhǔn)備與引入Vue2.1 CDN引入與本地文件引入之爭在傳統(tǒng)方式下引入Vue就兩種做法一種是直接用script標(biāo)簽引用CDN地址另一種是下載一個Vue的JS文件放到項目目錄里。CDN方式最簡單幾行代碼搞定!-- 開發(fā)版包含完整警告和輔助調(diào)試工具適合學(xué)習(xí)階段 -- script srchttps://cdn.jsdelivr.net/npm/vue2/dist/vue.js/script注意上面是Vue 2的寫法。如果你用的是Vue 3標(biāo)簽路徑略有不同后面會專門提到。這里我為什么要提Vue 2因為相當(dāng)一部分傳統(tǒng)項目和老代碼庫用的還是Vue 2而且很多開箱即用的UI框架在Vue 2下生態(tài)最穩(wěn)定。學(xué)習(xí)傳統(tǒng)開發(fā)方式時從Vue 2入手一天就能上手后續(xù)再看Vue 3也不會覺得困難。畢竟響應(yīng)式的核心思想和模板機制是一脈相承的。本地文件方式也簡單官網(wǎng)下載vue.js和vue.min.js兩個文件放到j(luò)s目錄下頁面里引用相對路徑就行script src/js/vue.min.js/script兩種方式怎么選我的建議是只要能聯(lián)網(wǎng)優(yōu)先用CDN。別問為什么省事。但要注意如果目標(biāo)項目是在內(nèi)網(wǎng)運行或者對首屏加載速度有極致要求那就下載到本地。下載到本地時記得開發(fā)時引vue.js有警告提示上線時引vue.min.js壓縮版。2.2 開發(fā)版與生產(chǎn)版的差異Vue的JS文件官方分了開發(fā)版和生產(chǎn)版。差別主要在開發(fā)版會帶上完整的警告信息、把所有的斷言和提示邏輯都保留生產(chǎn)版則把這些全部壓縮去掉體積小一大截。比如你在代碼里寫了不存在的屬性開發(fā)版會立刻在控制臺給你一條紅色的警告提示哪個組件、哪個位置出了問題生產(chǎn)版呢悄悄忽略什么都不報。所以自己學(xué)習(xí)的時候一定用開發(fā)版。我以前見過一位同事上線后某個數(shù)據(jù)一直顯示不出來排查了半天才發(fā)現(xiàn)生產(chǎn)版把警告都吞了要是開發(fā)時能看到警告一分鐘就能定位問題。具體CDN地址可以根據(jù)版本去官方文檔的安裝頁找到。記住一句話學(xué)的時候帶警告部署的時候換壓縮版這是兩條鐵律。2.3 創(chuàng)建第一個Vue實例hello world的又一個版本項目文件就三個一個HTML文件、一個JS文件或直接在HTML里寫、引入Vue。我習(xí)慣把Vue邏輯單獨放在一個app.js里HTML只負責(zé)結(jié)構(gòu)。先看HTML!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleVue傳統(tǒng)方式入門/title /head body div idapp p{{ message }}/p /div script srchttps://cdn.jsdelivr.net/npm/vue2/dist/vue.js/script script srcapp.js/script /body /html再看app.jsvar app new Vue({ el: #app, data: { message: 你好Vue } });打開瀏覽器頁面上就會顯示“你好Vue”。核心就三行el指定Vue實例掛載到哪個DOM節(jié)點data里存放響應(yīng)式數(shù)據(jù){{ message }}把數(shù)據(jù)渲染到模板里。當(dāng)你通過app.message 換個內(nèi)容修改數(shù)據(jù)時頁面會自動刷新不需要寫任何DOM操作代碼。這就是響應(yīng)式最直觀的體現(xiàn)。3. 模板語法花括號、指令和它們背后的邏輯3.1 Mustache插值把數(shù)據(jù)“放”進去上面用到的{{ message }}這種雙花括號的寫法在Vue里叫Mustache插值。它不只是放一個簡單的變量里面可以寫任何合法的JavaScript表達式注意是表達式不能是語句。p{{ number 1 }}/p p{{ message.split().reverse().join() }}/p p{{ isOK ? 條件為真 : 條件為假 }}/p這些寫法都是允許的。但不要太離譜比如在插值里寫一長串業(yè)務(wù)邏輯那就很糟糕了。模板的意義在于聲明結(jié)構(gòu)復(fù)雜的計算應(yīng)該挪到后面要講的計算屬性里去。還有一個細節(jié)插值是正確的做HTML轉(zhuǎn)義的。什么意思如果message的值是scriptalert(xss)/scriptVue在渲染時會把尖括號轉(zhuǎn)成lt;和gt;直接當(dāng)成字符串顯示出來而不會去執(zhí)行里面的腳本。這是安全底線千萬別用不需要轉(zhuǎn)義的v-html指令去渲染用戶輸入的內(nèi)容。3.2 v-bind與v-model屬性綁定的兩種形態(tài)花括號插值只能作用于標(biāo)簽體里的文本內(nèi)容如果你想動態(tài)綁定HTML標(biāo)簽上的屬性比如href、class、style就得用指令。指令是Vue模板里以v-開頭的那幾個特殊屬性。v-bind的作用就是動態(tài)綁定一個屬性a v-bind:hreflinkUrl點擊跳轉(zhuǎn)/a這里的linkUrl是Vue實例data里的一個數(shù)據(jù)項。Vue會把它的值設(shè)置為href屬性的最終值。這個指令太常用了所以Vue提供了非常流行的縮寫——直接用冒號a :hreflinkUrl點擊跳轉(zhuǎn)/a初看可能會有點奇怪但習(xí)慣之后你會發(fā)現(xiàn)寫代碼效率高了不少。v-model則是專門用于表單元素的雙向綁定。所謂雙向指的不只是數(shù)據(jù)變化會更新頁面頁面上的用戶輸入也會反向同步到數(shù)據(jù)里。比如input typetext v-modelsearchText用戶在輸入框里敲的每一個字符都會實時同步到searchText這個數(shù)據(jù)上。這在以前的原生開發(fā)里要監(jiān)聽input事件再手動賦值現(xiàn)在一行指令搞定也體現(xiàn)了Vue數(shù)據(jù)驅(qū)動思想的意義——你只管維護數(shù)據(jù)DOM層的變化全交給框架。3.3 v-on事件綁定用戶交互的入口有了數(shù)據(jù)綁定自然就需要事件處理。v-on用來綁定DOM事件同樣有縮寫用代替button v-on:clickhandleClick點擊/button button clickhandleClick點擊縮寫寫法/buttonhandleClick是Vue實例methods里定義的方法var app new Vue({ el: #app, data: { count: 0 }, methods: { handleClick: function() { this.count 1; } } });methods里的方法在Vue實例上可以直接通過this訪問到data里的數(shù)據(jù)。這是傳統(tǒng)方式寫交互最核心的技能之一。你可能會想為什么不直接在v-on后面寫個表達式其實也可以比如clickcount 1但復(fù)雜邏輯一定要提到methods里面去。模板里寫邏輯越少越好。4. 響應(yīng)式原理與數(shù)據(jù)驅(qū)動頁面為什么能自己動4.1 為什么數(shù)據(jù)改了頁面就更新Vue的響應(yīng)式機制是它最核心的競爭力。在傳統(tǒng)方式下這個機制在底層是怎么實現(xiàn)的以Vue 2為例它會把data里的每個屬性都經(jīng)過Object.defineProperty處理給屬性加上getter和setter。當(dāng)屬性被讀取時比如模板渲染Vue會記錄誰在依賴這個屬性當(dāng)屬性被修改時它就會通知所有依賴方去更新。打個比方這就好比一間教室里老師在黑板上寫了個通知“明天下午三點開會”。同學(xué)們模板中的各個位置看到這個通知后都會記在備忘錄里。老師后來又把通知改成“推遲到四點”只要改一次黑板所有記了備忘錄的同學(xué)都會按新時間去執(zhí)行不需要挨個通知。這正是Vue開發(fā)的思維模式你只需要修改數(shù)據(jù)界面上所有跟這個數(shù)據(jù)有關(guān)的部分都會自動跟著變化。4.2 data為什么推薦用函數(shù)返回既然提到了data就不得不提一個關(guān)鍵細節(jié)。對于最簡單的Vue實例data可以是一個對象data: { message: hello }但在后面會遇到的組件定義里data就必須是一個函數(shù)返回一個新對象。這么設(shè)計是為了保證每個組件實例都持有自己獨立的數(shù)據(jù)拷貝不會互相串味。這可能是很多初學(xué)者第一次寫組件死活不更新數(shù)據(jù)的根源兩個組件實例共享了同一個data對象。我建議從一開始就養(yǎng)成良好的習(xí)慣——即使是根實例也寫成函數(shù)返回的形式data: function() { return { message: hello }; }傳統(tǒng)方式的演示階段不改問題不大但一旦過渡到組件協(xié)作這個習(xí)慣能幫你少踩很多坑。4.3 計算屬性模板里少寫邏輯的利器模板里寫復(fù)雜表達式不僅看著亂每渲染一次還會重新計算一遍浪費性能。Vue為此提供了計算屬性computed。它看起來像是一個方法但本質(zhì)上是基于依賴緩存的屬性。var app new Vue({ el: #app, data: { firstName: 張, lastName: 三 }, computed: { fullName: function() { return this.firstName this.lastName; } } });模板里直接用{{ fullName }}把拼接邏輯放在計算屬性里清清楚楚。而且只要firstName和lastName沒有變化多次訪問fullName返回的都是緩存值不會重復(fù)執(zhí)行函數(shù)體。理解緩存機制是區(qū)分老手和新手的標(biāo)志之一。5. 條件渲染與列表渲染讓頁面結(jié)構(gòu)和數(shù)據(jù)對齊5.1 v-if與v-show條件渲染的二選一開發(fā)過程中經(jīng)常會遇到這種需求某個區(qū)塊在特定條件下顯示否則隱藏。Vue提供兩條指令來處理v-if和v-show。兩者的區(qū)別很多網(wǎng)上的文章都講過。這里我用最real的經(jīng)驗總結(jié)v-if是真正的條件渲染為false時DOM位置上就是空的什么都不會渲染v-show則是用CSS的display來控制顯隱元素始終存在于DOM中只是被隱藏了?;谶@個區(qū)別選擇依據(jù)就很清楚了如果這個條件在頁面生命周期內(nèi)幾乎不會變或者切換頻率極低用v-if能省下不必要的DOM渲染開銷。如果元素頻繁切換顯示狀態(tài)比如標(biāo)簽頁切換、下拉菜單展開收起用v-show避免反復(fù)創(chuàng)建和銷毀DOM的性能損耗。在實際項目里我一般默認用v-show除非該元素在初始渲染時就確實不需要出現(xiàn)或者加載成本很高。這個選擇直接影響用戶體驗。5.2 v-for列表渲染的核心套路動態(tài)列表是每個前端項目里逃不掉的需求。Vue用v-for指令來實現(xiàn)?;緦懛╱l li v-foritem in items v-bind:keyitem.id{{ item.name }}/li /ul對應(yīng)的數(shù)據(jù)data: { items: [ { id: 1, name: 蘋果 }, { id: 2, name: 香蕉 }, { id: 3, name: 橘子 } ] }Vue會遍歷items數(shù)組為每個元素生成一個li。在遍歷時可以拿到兩個參數(shù)值和索引li v-for(item, index) in items{{ index }} - {{ item.name }}/li這里的索引index是從0開始的常用于顯示序號。v-for還可以用來遍歷對象但實際開發(fā)中遍歷數(shù)組占90%以上。另外注意v-for通常配合key使用這個屬性極其重要。5.3 key屬性的價值別小看它key是Vue虛擬DOM算法的錨點。簡單說當(dāng)數(shù)據(jù)順序或內(nèi)容變化時Vue需要知道哪些節(jié)點是移動了、哪些是新增的、哪些是刪除的。如果沒有keyVue只能靠索引去匹配很容易出現(xiàn)復(fù)用錯亂尤其當(dāng)列表包含有狀態(tài)的子組件或表單輸入時后果是數(shù)據(jù)更新了但界面顯示的是舊內(nèi)容。選擇key值時盡量用業(yè)務(wù)上唯一的ID別用數(shù)組索引。為什么因為索引代表的是位置不是身份。比如刪掉列表第一項后面所有項的索引都變了Vue會以為整個列表都變了性能變差同時還會引發(fā)選中狀態(tài)錯亂。一個經(jīng)典的坑列表里每項有個輸入框你刪除了中間一項結(jié)果輸入框里的內(nèi)容錯位了這就是不寫key或亂寫key的典型后遺癥。6. 實操演練做一個可排序的課程篩選頁面理論講再多不如動手做一個能跑的例子。這節(jié)我們手寫一個小頁面把前面這些知識串起來。需求很簡單給一組課程數(shù)據(jù)做篩選和排序。6.1 頁面結(jié)構(gòu)與樣式還是老規(guī)矩一個HTML文件一個JS文件。先定義頁面布局div idapp h3課程列表/h3 div 搜索關(guān)鍵詞: input typetext v-modelkeyword 排序方式: select v-modelsortType option valuedefault默認/option option valuepriceAsc價格從低到高/option option valuepriceDesc價格從高到低/option /select /div ul li v-foritem in filteredList :keyitem.id {{ item.name }} - {{ item.price }}元 /li /ul p v-iffilteredList.length 0沒有符合條件的課程/p /div這個HTML里我們用到了v-model綁定輸入框和下拉框、v-for循環(huán)渲染、:key綁定唯一ID、v-if做空狀態(tài)處理。全部是通過數(shù)據(jù)驅(qū)動的形態(tài)完成的。6.2 數(shù)據(jù)處理邏輯JS部分直接上完整代碼var app new Vue({ el: #app, data: { keyword: , sortType: default, courses: [ { id: 1, name: JavaScript基礎(chǔ), price: 99 }, { id: 2, name: Vue實戰(zhàn)入門, price: 129 }, { id: 3, name: CSS高級技巧, price: 79 }, { id: 4, name: Node.js服務(wù)端開發(fā), price: 199 } ] }, computed: { filteredList: function() { var self this; var list self.courses.filter(function(item) { return item.name.indexOf(self.keyword) ! -1; }); if (self.sortType priceAsc) { list.sort(function(a, b) { return a.price - b.price; }); } else if (self.sortType priceDesc) { list.sort(function(a, b) { return b.price - a.price; }); } return list; } } });核心邏輯全都封裝在filteredList這個計算屬性里。先按關(guān)鍵詞過濾再按價格排序。模板里不需要寫任何復(fù)雜邏輯只需要引用filteredList即可。6.3 運行效果與優(yōu)化點打開頁面你會看到四個課程。在搜索框里輸入“Vue”列表只會剩下“Vue實戰(zhàn)入門”把排序方式切換為“價格從高到低”課程順序會立刻反過來。全程沒有任何DOM操作所有改變都是數(shù)據(jù)驅(qū)動的。為什么用computed而不是methods因為數(shù)據(jù)不變時計算屬性有緩存搜索和排序的結(jié)果不需要重復(fù)計算。如果每次渲染都調(diào)用一個方法性能會白白浪費在一些很簡單的運算上。這個案例還可以繼續(xù)擴展加篩選分類、加分頁、加數(shù)量統(tǒng)計等等。但核心模式已經(jīng)展示出來了——數(shù)據(jù)和模板的聲明式綁定加上計算屬性的數(shù)據(jù)加工鏈路這就是傳統(tǒng)方式最常見的開發(fā)思路。7. 常見問題排查與個人經(jīng)驗補充7.1 頁面顯示原始代碼Vue沒有生效剛接觸時很容易遇到的麻煩把HTML打開模板上的{{ message }}原樣出現(xiàn)在頁面上就跟普通文本一樣。排查思路按照優(yōu)先級來瀏覽器控制臺有沒有報錯如果提示Vue is not defined基本就是腳本沒有成功加載。檢查CDN地址是否能訪問、是否因為網(wǎng)絡(luò)原因被攔截。el選擇器是否對應(yīng)正確如果實例掛載的節(jié)點ID寫錯了Vue找不到掛載點同樣不會生效。腳本引入順序是否正確Vue的JS必須在app.js之前引入。因為app.js里用了new Vue()執(zhí)行的時候如果Vue變量還不存在就直接報錯停了。很多情況下最后一個順序問題最隱蔽。HTML的script是按順序加載的這種傳統(tǒng)的依賴關(guān)系必須靠手動保證一定要在JS業(yè)務(wù)代碼之前引入Vue本體。7.2 數(shù)據(jù)更新了但視圖沒變這種問題排查起來更讓人頭疼因為你已經(jīng)修改了數(shù)據(jù)console里面打印也正常但界面上就是沒變化。第一步控制臺看Vue實例是否有警告信息。開發(fā)版Vue在數(shù)據(jù)異常時會給出提示比如增刪數(shù)組元素這類操作Vue 2里需要特殊寫法的。為什么因為之前提到的defineProperty機制對數(shù)組的新增索引、length變更等操作監(jiān)聽不到必須用Vue提供的方法來觸發(fā)更新比如數(shù)組.push(...)、Vue.set等。傳統(tǒng)方式下我建議data里的數(shù)組要變更時優(yōu)先用push、splice這些數(shù)組方法而不是直接用arr[i] newValue這種索引賦值。否則視圖可能不動你還一臉懵。7.3 使用瀏覽器開發(fā)者工具輔助定位用傳統(tǒng)方式開發(fā)Vue瀏覽器插件工具會在頁面里注入一個Vue面板可以實時查看某個實例的data、computed、methods等狀態(tài)。在排查問題時非常好用。發(fā)現(xiàn)頁面數(shù)據(jù)不對時我一般先打開這個面板看數(shù)據(jù)本身對不對判斷問題到底出在“數(shù)據(jù)層”還是“渲染層”。如果數(shù)據(jù)是對的再看模板綁定如果數(shù)據(jù)本身就不對先修數(shù)據(jù)邏輯。這種分層排查的思路在復(fù)雜項目里能幫你節(jié)省一半的時間精力。7.4 老生在實踐中的幾點體會最后說點個人經(jīng)驗。用了快十年的Vue我很慶幸當(dāng)年是先接觸傳統(tǒng)引入方式再學(xué)的構(gòu)建工具。直接裸寫Vue讓我對組件化、響應(yīng)式、模板生命周期都有更本質(zhì)的理解而不是模板生成器的一個操作員只會復(fù)制粘貼。第一這種模式最適合練習(xí)“數(shù)據(jù)驅(qū)動思維”。沒有額外腳手架干擾每次交互都要自己定義數(shù)據(jù)再把數(shù)據(jù)和模板綁定好。思考路徑極其清晰。第二不要以為傳統(tǒng)方式等于低效。配合閱讀官方文檔和API完全能寫出結(jié)構(gòu)優(yōu)雅、性能達標(biāo)的完整項目。很多老系統(tǒng)到今天還在使用類似方案維持著幾百上千個頁面。第三這系列后續(xù)我還打算講講Vue組件怎么寫、自定義指令和插槽怎么用以及如何手寫輪子實現(xiàn)一個輕量級狀態(tài)管理方案。只要你把第一篇的底子打扎實了后面就是水到渠成的事。平時我寫個人小工具或者做臨時驗證的時候都是直接拖一個Vue CDN鏈接進HTML連開項目都不會開。哪怕只是為了能快速驗證一個想法掌握這種原汁原味的Vue開發(fā)玩法怎么算都不虧。