[Vue 3] 第一課 認識前端框架和Vue 3
Adrian Chen

什麼是前端框架

儘管我們的前端三件套(HTML、CSS和JavaScript)已經非常強大,能夠完成所有的前端開發工作,但在有的時候,一些開發過程也稍顯繁瑣。例如,我們有下面一個例子:

從後端請求到一個list,需要把這個list中的每一項內容都渲染到頁面上。

這是一個極爲常見的需求。如果我們使用三件套來實現它,我們需要這樣去寫:

1
<div id="father"></div>
1
2
3
4
5
6
7
8
9
10
// 假設下面的變數list就是我們得到的list
let list = ["小王", "小李", "小張", "小劉"]

let father_node = document.getElementById("father")

for (let i = 0;i < list.length;i ++) {
let child_node = document.createNode("div")
child_node.innerHTML = list[i]
father_node.appendChild(child_node)
}

這樣的邏輯顯然是明瞭的。

然而,讀者是否認爲這樣的步驟太過於繁瑣?我們爲了將一個列表動態渲染到頁面上,竟然要手動地一個一個創造節點、添加節點。儘管有for迴圈幫助我們,這也顯得過於繁瑣了些。

框架(framework)就是爲我們解決類似問題的一個解。

所謂的框架,就是將一些複雜但是沒有什麼親自書寫必要的步驟封裝起來,開發人員只需要聚焦於具體功能的實現,其餘的交給框架來做即可。框架的出現大大提高了開發效率,避免了開發人員將大量時間耗費在沒有意義的冗餘程式碼中。

比如上面的這個例子,如果使用框架來做,什麼獲取父節點,什麼創建子節點之類的步驟統統都可以省略掉。如果使用Vue框架,僅需如下程式碼即可:

1
2
3
<div id="father">
<div v-for="item in list">{{ item }}</div>
</div>
1
let list = reactive(["小王", "小李", "小張", "小劉"])

至於這段程式碼是什麼意思,讀者暫時不需要去了解它,在之後的學習中,筆者會詳細爲您講述。

從上面這個例子中,我們不難看出,使用框架前後的代碼開發難度簡直是改天換地的。這也是我們使用框架最重要的意義所在。

目前在前端比較流行的框架有Vue、React、BootStrap等。後端比較流行的框架有Springboot(Java)、Gin(Go)、Flask(Python)等。本系列課程使用的框架是Vue(音標/vju:/)。使用的框架版本是Vue 3.0。

Vue 3框架簡介

在遙遠的東南亞,有一個神奇的國家叫馬來西亞,這個神奇的國家有兩塊土地,分別是半島部分和婆羅洲島部分——但這不重要。重要的是在這兩部分中間,夾着一個面積極小的國家,這個國家就是大名鼎鼎的新加坡。在這個小國家中,生活着一個偉大的人類,這個人類幾乎以一己之力賞了全球近一半的前端程序員的飯。這個偉大的人類,名字叫做Evan You,或者作爲中國人,我們更熟悉他的中文名——尤雨溪

尤雨溪,Vue框架和Vite構建工具的作者,全球一半前端程序員心中祖師爺一般的存在。此人長期混跡於各大論壇,在中國社群媒體上極受歡迎。

他所發展的Vue框架一經問世即風靡全球。 其後推出的Vue 3版本雖然基本上不相容之前的Vue 2.0,但得益於Vue 3的強大特性和與Vite工具結合之後令人難以置信的構建速度,Vue 3正在快速搶佔前端程式設計師的心臟。

Vue 3框架非常簡單、高效、直觀且快速。Vue 3官網對它的自我介紹是“The Progressive
JavaScript Framework”,漸進式JavaScript框架。

image

上圖這個花裏胡哨像夜店一樣的主頁就是Vue 3項目的主頁。由於尤雨溪大佬是新加坡華人,故Vue 3的文檔也有很舒適的中文版本。在接下來的學習中,我們將主要使用Vue 3的官方文檔。

Vue 3專案建立

Vue官方推薦使用自家的Vite構建工具來搭建Vue 3專案,筆者亦推薦這種方式。因爲這種方式非常便捷,且搭建出來的項目默認使用Vite構建工具,該工具以其高速準確的構建水平而享譽世界。搭建過程如下:

  1. 驗證npm安裝:
1
npm -v

如果輸出了npm的版本號,證明npm安裝成功。這一步相信讀者已經完成了。

  1. 初始化vue 3項目:
1
npm create vite@latest

輸入專案名稱,然後選擇vue這個專案模板。我們暫時不使用TypeScript,而是繼續使用JavaScript進行開發。

  1. 進入專案內部,執行npm install
1
2
3
# 進入某個目錄
cd <專案名稱>
npm install

這樣,一個vue 3專案就已經被new了出來。

專案目錄解析

建立好專案之後,我們會得到以下專案目錄:

image

下面我們來具體來看一下目錄結構。

flake.lock和flake.nix

這兩個檔案是筆者使用的作業系統Nix OS特有的,用於配置該專案在Nix OS上的開發環境,讀者不會有這兩個文件,忽略即可。

index.html

這個檔案我們已經非常熟悉了,這是最後放到瀏覽器裏面的HTML檔案。在Vue中,我們是透過Mount這個操作,將Vue框架的程式碼的編譯結果交給HTML去執行。因此,我們需要在這裏明白一件事情:任何框架,都只是簡化了開發過程,而不是改變三件套的底層邏輯。 也就是說,瀏覽器根本不認識Vue框架,它只認識前端三件套。Vue框架的程式碼必須編譯成前端三件套,才能夠被瀏覽器識別到。所以在這個專案結構中,纔會有index.html檔案的出現。

package.json和package-lock.json

這兩個檔案讀者不應該陌生。它們是node.js的依賴管理檔案。當我們使用npm install的時候,依賴會自動寫入這兩個檔案,並鎖定版本,確保專案的可復現性。大家可以將其理解成一個依賴索引,之後在不同的電腦上重建專案的時候,只需要按照索引去npm倉庫裏面找依賴就可以了。

public目錄

這個目錄用來存放一些static的資源,例如圖片、影片等。

README.md

專案自述文件,可以暫時忽略。

vite.config.js

Vite構建工具的配置檔案,構建過程中的一些option可以在其中定義。

src目錄

項目的主要目錄! 我們大部分時間都將在這個目錄中書寫我們的程式碼。

src/App.vue

項目的根component,在學習Vue的前期,我們主要在這個檔案中深耕,書寫我們的程式碼。

src/assets

也用於存放static檔案,在前期不推薦讀者使用這個目錄,因其在構建過程中會出現路徑問題,故需要特別配置。在讀者對Vue和JS足夠熟悉之前,建議大家直接使用public目錄。該目錄可以直接刪除。

src/components

component目錄。在Vue中,一個極其重要的思想是:一切皆component。關於component的概念,將在後面詳細介紹。

src/main.js

專案的主要配置檔案。在這裏我們可以定義全局component,管理npm軟體包的全局使用等等。

src/style.css

專案的全局css,可以用於所有的component。

專案啓動

我們可以透過:

1
npm run dev

來啓動一個開發伺服器。該伺服器默認運行在http://localhost:5173/ 位址。修改將自動熱更新,不需要手動更新頁面,也不需要手動啓停伺服器。

Vue 3基礎部分

模板語法

Vue 3第一個強大的點在與其擁有一套高效且簡介的模板語法。我們之前的那個例子就是應用了這套模板語法。

通俗來講,所謂模板語法,就是能夠將JavaScript中的資料快速動態渲染到HTML中。

Vue 3提供的模板語法包括下面幾類:

  1. 文本插值:
1
<span> {{ msg }} </span>

msg爲需要插入到這個地方的JS變數值。同時,每次該值更新,該處內容也會同步自動更新。

  1. 屬性綁定:

文本插值是將JS變數直接插入到對應的HTML標籤之間。那麼標籤的屬性有沒有可能使用JS變數動態去調整呢?當然也是可以的。語法如下:

1
<span :id="id_js"></span>

我們可以看到,只要在屬性名稱之前加一個:,就可以在後面使用JS變數作爲屬性的值。同樣的,變數值改變,屬性值也會同步改變。

這個模板語法被大量使用,其中有一種是極爲常用的,那就是Boolean值的綁定。例如,我們想要一個按鈕在variable(Boolean)變數值爲真的時候啓用,其餘情況禁用。我們就可以這樣來做:

1
<button :disabled="!variable"></button>
  1. 使用JavaScript表達式

至此,我們僅在模板中綁定了一些簡單的屬性名稱。 但是 Vue 實際上在所有的資料綁定中都支援完整的 JavaScript 表達式:

1
2
3
4
5
6
7
{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

每個綁定只支援單一表達式,也就是一段能夠被求值的 JavaScript 程式碼。 一個簡單的判斷方法是是否可以合法地寫在return關鍵字後面。

1
2
3
4
5
<!-- 這是一個語句,而非表達式 -->
{{ var a = 1 }}

<!-- 不支援條件控制,請使用三元表達式 -->
{{ if (ok) { return message } }}
  1. 呼叫函式
1
2
3
<time :title="toTitleDate(date)" :datetime="date">
{{ formatDate(date) }}
</time>

其中title的值就是toTitleDate()這個函式的返回結果。

ref()和reactive()

渲染到HTML模板上的JS變數,必須使用ref()reactive()包裹。我們稱之爲響應式變數 。只有使用ref()reactive()包裹的變數,才能夠在其發生改變時,自動觸發HTML模板的同步改變。

一般來講,當所渲染的資料類型爲比較簡單的類型,譬如字串、數字、Boolean等的時候,我們使用ref()包裹;當其比較複雜,譬如數組、物件的時候,我們使用reactive()包裹。但是在初級階段,筆者推薦所有需要渲染的變數均使用ref()來渲染

接下來我們來看一個例子:

1
2
3
4
5
6
7
8
9
import { ref } from 'vue';

let variable = ref("hello, world.")

const show_var = () => {

// 在JavaScript部分使用variable變數的值,必須使用.value來取到!!
console.log(variable.value)
}
1
2
<!-- 在HTML模板部分使用模板語法,不能使用.value -->
<div>{{ variable }}</div>

計算屬性

模板中的表達式雖然方便,但也只能用來做簡單的操作。 如果在模板中寫太多邏輯,會讓模板變得臃腫,難以維護。 比如說,我們有這樣一個包含巢狀數組的物件:

1
2
3
4
5
6
7
8
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})

我們想根據 author 是否已有一些書籍來展示不同的訊息:

1
2
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>

如果在模板中需要不只一次這樣的計算,我們可不想將這樣的程式碼在模板裡重複好多遍。

因此我們建議使用計算屬性來描述依賴響應式狀態的複雜邏輯。

重構之後的程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})

// 一個計算屬性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>

綁定class和style

前面我們講到了模板語法中存在着對屬性的綁定,而class和style嚴格來講,當然也是HTML標籤的屬性。但是這兩個屬性同一般的屬性來講有所不同。

class的動態綁定

通常用於動態綁定某個類別。即在某個條件滿足的情況下綁定該類別,而不滿足的情況下則不綁定。語法如下:

1
<div :class="{ class_name : isActive }"></div>

其中,class_name是需要綁定的類別,而isActive則是一個Boolean類型的響應式JavaScript變數。在isActive爲true時,渲染如下:

1
<div class="class_name"></div>

同理,在isActive爲false時,渲染如下:

1
<div></div>

style的動態綁定

style屬性的動態綁定則通常用於希望某個css屬性的值是動態的JavaScript變數

例如,我們希望將如下div的style中color同JS變數color_div綁定起來,我們可以這樣做:

1
<div :style="{ 'color' : color_div }"></div>

這樣,當color_div的值發生改變,該div的style中color值也會相應改變。

條件渲染

請讀者的回想一下,以下場景是否在之前的開發過程中經常遇到:希望某一個元素在需要的時候顯示,但在不需要的時候消失?

答案是肯定的。例如,我們的結業功課中,有一項要求是:當點擊某個按鈕之後,頁面中的某個元素消失掉,重新點擊那個按鈕,元素又回來。

在之前的開發過程中,讀者想到了五花八門的方法來解決這個問題,有使用JS動態添加和刪除元素的(這也是我們推薦的標準方法),有使用JS處理style,讓元素移出屏幕之外的(很不錯的想法,在實際開發中也經常這樣使用),甚至還有寫另一個沒有該元素的頁面和當前頁面互相切換的(這個方法有點子差,但的確是劍走偏鋒)。在Vue框架中,我們使用一種更爲簡便的方法來實現這一目標,那就是條件渲染。

條件渲染的標誌是v-ifv-else。例如,我們需要在isActive變數爲true時顯示div,而在其爲false時不顯示它,我們只需要這樣寫:

1
<div v-if="isActive"></div>

如果我們想要讓這個div不顯示的同時顯示另一個元素,我們當然可以這樣寫:

1
2
<div v-if="isActive"></div>
<div v-if="!isActive"></div>

這樣,當第一個div不顯示的時候,第二個div將會顯示;當第一個div顯示的時候,第二個div將不會顯示。

如果這兩個div緊緊靠在一起,那我們就可以這樣簡化之:

1
2
<div v-if="isActive"></div>
<div v-else></div>

使用v-else的要求是:使用v-else的元素必須緊跟在使用v-if的標籤之後。因爲Vue透過這種方式判斷這個v-else屬於哪個v-if。這很類似JS中的if-else語句,else關鍵字也是跟在if後面的。

列表渲染

列表渲染適用於什麼樣的場景呢?適用於我們這節課最開始的那個例子:現在有一個list,需要把這個list中的每一項內容都渲染到頁面上。

爲了方便讀者觀察,我再次把之前的程式碼貼到這裏來:

1
2
3
4
5
6
7
8
9
10
// 假設下面的變數list就是我們得到的list
let list = ["小王", "小李", "小張", "小劉"]

let father_node = document.getElementById("father")

for (let i = 0;i < list.length;i ++) {
let child_node = document.createNode("div")
child_node.innerHTML = list[i]
father_node.appendChild(child_node)
}

以上是使用原生三件套實現該任務所執行的程式碼。

Vue中,實現這個任務再簡單不過:使用v-for。例如上面的例子:

1
<div v-for="item in list">{{ item }}</div>

我們來解釋一下:

v-for關鍵字表示列表渲染,即將list中的每一項渲染到HTML中。

item in list表達式:item是臨時的一個變數,名字可以隨便起,就像定義一個變數一樣。它僅在此列表渲染中有效,在被渲染元素和該元素的所有子元素中適用,代表列表中的每一個列表項。in關鍵字很直觀,不多解釋。list即爲需要渲染的list的變數名稱。

{{ item }}是模板語法,表示將item這個臨時變數的值渲染到這個位置。

上述列表渲染的結果是:

1
2
3
4
<div>小王</div>
<div>小李</div>
<div>小張</div>
<div>小劉</div>

事件處理

對應原生JS中的事件處理。在原生JS中,我們是透過addEventListener()函式來監聽某個元素觸發的事件,例如click、mouseenter、mouseleave等。在Vue中,我們可以直接在元素上添加監聽,並將該監聽綁定到某個函式上:

1
<div @click="func"></div>
1
2
3
const func = () => {
// func
}

按鍵修飾符

滑鼠的click事件可以對應着三種情況:點擊左鍵,點擊右鍵,點擊中間鍵。當然,讀者要是非說Apple的滑鼠只有一個按鍵,那我也不能多說什麼(笑)。

那麼如何表示這三種情況呢?這就需要使用到按鍵修飾符的概念。例如:

1
2
3
<div @click.left="clickLeft"></div>
<div @click.right="clickRight"></div>
<div @click.middle="clickMiddle"></div>

還有許多按鍵修飾符,讀者可以自行參考Vue官方文檔。

表單輸入綁定

當我們輸入一段文字、點選一個按鈕,或者選擇一個抽屜的時候,我們會將選擇的結果放到JS變數中去。我們希望這個過程是自動的。例如,我們在輸入文字的時候,總是希望每輸入一個字元,對應的JS變數都會自動更新爲這個值。在原生三件套中,我們只能夠這樣寫:

1
2
3
4
5
6
let val = ""

let input_box = document.getElementById("input_box")
input_box.addEventListener("input", (e) => {
val = e.target.innerHTML
})
1
<input id="input_box">

如果我們需要雙向綁定呢?即當val的值發生改變的時候,input輸入框中的內容也可以放生改變。坦白講,筆者也不會寫。但是筆者找到了一個看起來還算OK的解決方案,讀者可以感受一下:

image

這顯然是極爲恐怖的。而在Vue下,框架用一個屬性——v-model即幫你實現了表單元素與JS變數的雙向綁定:

1
<input v-model="val">

這樣,當input有輸入的時候,val將會同步更新爲輸入的內容;當val發生改變的時候,input輸入框中的內容也會同步更新。

關於其他表單元素的雙向綁定,請讀者自行查閱Vue官方文檔。

偵聽器

我們有的時候希望在某個變數的值發生變化的時候,自動執行某個函式。那麼我們即可使用Vue提供的偵聽器——watch()。下面的程式碼表示偵聽變數variable,當其值發生改變的時候,自動將新值列印出來。

1
2
3
watch(variable, (newValue) => {
console.log(newValue)
})

watch()函式包括兩個引數:第一個是要偵聽的對象,第二個是一個回呼函式,代表偵聽對象發生改變的時候需要執行的動作。

生命週期

這個概念十分重要!!!

我們先來了解一下頁面從生到死的全過程:

  • 創建(create):指某個頁面剛剛被創建出來,元素還沒有開始渲染,JS剛剛開始執行。
  • 掛載(mount):指頁面中元素渲染的過程。
  • 更新(update):頁面中元素渲染完成後某些元素髮生更新的過程。
  • 卸載(unmount):指頁面中元素解除渲染的過程。
  • 消亡(destroy):指頁面完全消亡。

這一整個過程,包含了這個頁面從生到死的全過程,稱之爲這個頁面的生命週期

我們可以將我們的函式添加到這個生命週期的任意位置,並且在該時刻執行。下圖表示了Vue中頁面的生命週期:

image

結語

Vue的基礎部分到這裏基本就講得蠻全面了。在這裏給讀者提幾點學習Vue的建議:

  1. 熟讀Vue官方文檔,由於官方文檔有順暢的中文版本,所以這是我們閱讀很大的優勢。Vue官方文檔永遠都是最新、最權威、最完整的Vue學習資料。當我們遇到問題的時候,去翻一翻官方文檔,說不定就找到了解決辦法。

  2. 和原生三件套的寫法做對比。Vue作爲前端框架,自然簡化了大量原生的開發工作。因此在學習一個Vue特性的時候,我們腦中可以舉一個例子,然後分別使用原生三件套和Vue來實現,這樣我們就可以很清楚地知道Vue到底可以應用到什麼地方,又簡化了什麼。

  3. 多造訪一些著名的論壇。遇到問題,一找官方文檔,二找Google,三找論壇,四找GPT,這也應該成爲讀者經過訓練之後的一個條件反射。