Vue 學習筆記(1):從傳統 JavaScript 到 Vue 開發

AurLemon發表於2024-08-31

前言

筆者在學習 Vue 等前端框架前只接觸過基本的前端三件套,即 HTML、CSS、JavaScript(原生),在這之前有嘗試接觸過一些 Vue 教程,瞭解一些語法,但並不知道他們背後到底是什麼關係。

近些日子硬著頭皮寫了幾個 Vue 專案,有所心得。好歹是把 MVVM 和工程化之類的概念過了一遍,便想著把自己對這些概念的理解用教程的方式記錄一下!

這篇文章算是一個學習筆記和心得整理(但其實自己是按著入門教程的樣子寫的),順帶練練文筆。也希望能幫到恰好有類似基礎、但還未學習 Vue 的朋友(真的有人看??)。

本文以 Vue 2 進行演示,只著重說明 Vue 的一些基本語法和概念,以及過渡到前端工程化專案相關的內容。具體的語法還是推薦從 Vue 官方文件入手。本文比較適合學習過前端三件套並且對 JavaScript 有大致瞭解,並想開始進入 Vue 學習的朋友鑑賞!更建議對 JavaScript 有一定了解的(特別是建構函式、物件、原型這一塊的)來。歡迎各位朋友勘誤!!!

Vue 基本概念和語法

引題

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>簡單示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;  // 設定字型
            text-align: center;  // 設定文字對齊方式
            margin-top: 50px;  // 設定上外邊距
        }

        #alertButton {
            padding: 10px 20px;  // 設定內邊距
            font-size: 16px;  // 設定字型大小
            cursor: pointer;   // 設定指標樣式
        }
    </style>
</head>
<body>
    <button id="alertButton">點選我</button>

    <script>
        // 透過 document.getElementById('alertButton') 獲取 HTML 元素
        // 隨後給這個 HTML 元素新增點選事件的監聽器,事件被觸發後的邏輯程式碼在箭頭函式內
        document.getElementById('alertButton').addEventListener('click', () => {
            alert('按鈕被點選了!');
        });
    </script>
</body>
</html>

在直接開始 Vue 之前,先來重溫一下一個網站的頁面最基本的三要素:HTML、CSS 和 JavaScript。HTML 用標籤來定義頁面的結構和內容(如 <p></p><div></div>);CSS 用選擇器和宣告來定義頁面的樣式(如p { color: red; });JavaScript 是一門程式語言,用於定義網頁的行為和互動。

從語言的定義上來說,HTML 和 CSS 不是程式語言,只有 JavaScript 是程式語言。因為 HTML 和 CSS 只是告訴瀏覽器如何渲染、生成頁面,頂多算標記語言。而 JavaScript 他有邏輯控制、有函式、有變數和資料型別,JavaScript 誕生之初就是為了操作網頁頁面中的內容而誕生的。

HTML 是最基本的、定義結構的東西,因為不論是 CSS 還是 JavaScript,都是需要透過 HTML 標籤引入到頁面裡的,CSS 是給 HTML 變樣子的樣式表語言,而 JavaScript 是操作頁面內 HTML 的程式語言(是的,JS 不能直接操作 CSS 檔案和樣式表,它只能透過修改 HTML 的屬性來實現對樣式的變更,如 class 屬性和 style 屬性)。

Vue 是什麼

複習完基本的知識,讓我們回到 Vue。Vue 到底是什麼?

Vue 是一個 JavaScript 框架。所謂“框架”,就是在原有的基礎上又搭了一個框架,你只需要在這個框架裡做你自己想做的東西就好了。換句話說,你甚至不用學習 JavaScript,只學習 Vue 的語法都夠了(當然這樣學習很不推薦,因為 Vue 本身就是基於 JavaScript 繼續開發的),因為 Vue 已經為你封裝了大量的常見功能和模式,你只需要在裡面用就好了。當然,除了 Vue 以外,還有很多前端框架,這些框架的寫法和理念都不太一樣(如 React、Angular 等),但最終的目的肯定都是為了方便

Vue 和傳統 JavaScript 的區別

傳統 JavaScript 實現

欸,為什麼說方便?傳統的 JavaScript 有什麼缺點呢?先不急著說原因,我先假設自己要做一個 計數器 頁面,然後分別用傳統的 JavaScript 和 Vue 框架寫一段程式碼,來觀察觀察看它們的區別。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>原生 JavaScript 計數器示例</title>
</head>
<body>
    <h1 id="counter">計數器:0</h1>
    <button id="incrementButton">增加</button>
    <button id="decrementButton">減少</button>

    <script>
        // 初始化計數器
        let count = 0;
        
        // 獲取 DOM 元素
        const counter = document.getElementById('counter');
        const incrementButton = document.getElementById('incrementButton');
        const decrementButton = document.getElementById('decrementButton');

        // 更新計數器顯示
        function updateCounter() {
            counter.textContent = `計數器:${count}`;
        }

        // 增加計數
        incrementButton.addEventListener('click', () => {
            count++;
            updateCounter();
        });

        // 減少計數
        decrementButton.addEventListener('click', () => {
            count--;
            updateCounter();
        });

        // 初始化顯示
        updateCounter();
    </script>
</body>
</html>

這個是傳統的 JavaScript 的寫法,它的流程大概是這樣的,首先在 HTML 裡面放一個標籤用來顯示計數,另外兩個按鈕用來增加和減少計數;隨後在 JS 中放好一個變數用來存值,再根據兩個按鈕的 id,透過document.getElementById();方法拿到這兩個按鈕元素,之後給這兩個按鈕分別新增監聽器,一個增加一個減少。最後寫一個函式用來更新 HTML 裡的內容textContent,每次監聽器裡面都會呼叫一下這個函式,這個函式也會對應的修改<h1 id="counter"></h1>裡面的內容。

Vue 實現

看著很正常,那麼 Vue 寫起來是啥樣子的呢?讓我們來看看:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue 2 計數器示例</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<body>
    <div id="app">
        <h1>計數器:{{ count }}</h1>
        <button @click="increment">增加</button>
        <button @click="decrement">減少</button>
    </div>

    <script>
        new Vue({
            el: '#app',
            data: {
                count: 0
            },
            methods: {
                increment() {
                    this.count++;
                },
                decrement() {
                    this.count--;
                }
            }
        });
    </script>
</body>
</html>

這看起來和傳統的 JS 差距有點大,讓我來結合傳統的 JS 穿插著解釋一下。

剛開始,Vue 框架透過<script></script>標籤中 src 的地址被瀏覽器引入了!(是的啊,就是這麼簡單直接引入的,這在 Vue 的文件中被稱為 CDN 引入。再深入瞭解一點的朋友可能會問了,那 NPM 呢,Vue CLI 呢,SPA 呢,這個別急,後面再說 )

然後,還是老樣子,HTML 上留好一個標籤就好了!一個<h1></h1>和兩個按鈕<button></button>。但是奇怪的是,為什麼到了 Vue 這裡好像有點不一樣呢?<h1></h1>裡面的“計數器”後面為什麼多了個{{ count }}?為什麼 button 的後面多了個 @click?為什麼外面還要套一層<div id="app"></div>

Vue 語法

這其實都是 Vue 自己的語法!很簡單,{{ count }}@click都被稱為模板語法{{ count }}其實就是把一個名為 count 的資料的值直接放到 HTML 內,這種寫法被稱為插值表示式。是的,Vue 支援直接把資料透過一對花括號{{}}放到 HTML 中哦!和傳統的 JS 相比,你不用費時費力再去直接操作 HTML(DOM)了,哪裡還需要寫什麼 textContent、innerHTML?Vue 的資料繫結系統已經幫你處理好了。你擔心,如果 count 的值增加或者減少了怎麼辦?沒關係啊,HTML 內的資料也會自動更新的,因為已經和這個變數雙向繫結了。

那麼@click又是什麼?你可能會覺得很眼熟,這不是傳統 JS 裡的onclick=""嗎。是的!這就是繫結到了按鈕到了一個方法上。前面的 “@” 是 v-on 的縮寫(因為用的太頻繁,所以有縮寫),如果完全去掉縮寫,那麼他的樣子就是v-on:click="increment"。你會驚訝,這個 “v-” 打頭的 v-on 是啥?這叫 Vue 指令,用來對 HTML(DOM)元素進行動態操作和資料繫結。這裡的 v-on:click 就是給被新增的 HTML 標籤新增一個 click 事件

v-on 除了 click,還有 mousedown、mouseup 等事件。除了 v-on,還有另一種指令叫 v-bind,用於動態繫結值,它的縮寫是一個冒號 “:”。如<div v-bind:class="className"></div>,也就是繫結一個 CSS 類到一個名為 className 的資料中,這個 div 的類會隨著資料 className 值的變化而變化,並且,除了繫結 class,如果在 img 標籤上,你還可以動態繫結圖片連結 src <img :src="imageURL" />。除了 v-on、v-bind 以外,還有 v-for(迴圈用的,遍歷陣列或物件,並渲染列表項的)、v-if、v-show。當然,還有非常非常多的 Vue 指令,我這裡是說不完的,如果你有興趣,可以去 Vue 的官方文件看看,那邊有詳細的解釋和案例。

Vue 例項

那最外層的 <div id="app"></div>又是什麼?這個需要結合 script 標籤的程式碼部分一起看。讓我們把視角聚焦到程式碼塊。

new Vue({
    el: '#app',
    data: {
        count: 0
    },
    methods: {
        increment() {
            this.count++;
        },
        decrement() {
            this.count--;
        }
    }
});

不同於直接在 script 標籤內編寫程式碼,Vue 是透過建立 Vue 例項然後在其中傳入一個物件後的。可以看作是往new Vue()裡面傳遞了一個配置物件,這個物件包括了各種選項。比如 el、data、methods 之類的。

如果你分得清函式、方法、建構函式、物件例項、類,這段話就不用看了。如果分不清,最好去補全一下 JavaScript 基礎,這裡只能簡單說一下。總而言之,只需要把new Vue()的過程當作是告訴 Vue 如何去做就比較好理解了,我們只是透過script標籤引入了 Vue,但是並沒有去使用它,所以需要一個方式來構造它。所以,new Vue()是呼叫一個名為 Vue 的建構函式,在這個函式內部傳入一個負責配置的物件,即{ el: '#app', data: { count: 0 }, methods: { increment() {} } }。這裡麵包含了掛載點el、資料data等內容。傳遞完後,建構函式會建立物件例項。本質上,new Vue()建立 Vue 例項的過程就等於在寫配置檔案,去告訴 Vue.js 要做的事情。)

el處的值是 #app,即<div id="app"></div>,這也是解答了我們上方的疑問。el 即 Vue 的掛載點。我們先前所說的資料雙向繫結、HTML 指令都是 Vue 框架所帶的功能,你如果直接寫在 HTML,原生 JavaScript 環境是不支援也無法識別的。簡單來說,只有將內容寫在 Vue 的掛載點內,才能被解析,實現想要的效果(也就是說除了 #app 以外的地方都不會被 Vue 渲染,你隨便寫東西不會被 Vue 捕獲的,這也是為什麼 Vue 也被稱為“漸進式框架”,因為你可以指在頁面的一小塊地方引入他,如果有需要再漸進式的增加)。

順帶一提,文章從此處開始,不會在繼續使用 HTML 指代頁面結構,而是使用更加準確的 DOM(文件物件模型)一詞稱呼。DOM 也是 JavaScript 組成的一部分,除了 DOM 以外,還有 BOM(文件物件模型)及 ECMAScript。

迴歸正題,data就是定義資料值的地方。簡單理解為在這裡宣告變數,就能實現前面所說的,雙向繫結的效果。methods則是定義方法的地方,也就是傳統 JavaScript 程式設計中的函式(物件的函式),寫法也與函式宣告類似,只是不需要 function 開頭了。此外,你可能會注意到,increment() 和 decrement() 調整 count 值的寫法似乎有些不同——它的前面有一個 this。在 Vue 內部,this 指向的是當前 Vue 例項,因此你可以透過 this 訪問和操作例項的資料和方法。this.count 代表例項上的 count 屬性,這樣就可以實現資料的修改和檢視的更新。

你可能會驚訝,那不是什麼做什麼都要建立一個方法了嗎?並不是,Vue 也有類似於傳統 JavaScript 程式設計中“頁面載入後執行”的方法,這被稱為鉤子 Hooks(實際上這種給“某件事做完以後插入自定義邏輯”的操作都叫 Hooks,不管是 Vue 還是其他語言,在程式設計中這是一種通用的概念)。Vue 中比較常用的 Hooks 有 created()、mounted()、beforeDestroy()。

new Vue({
    el: '#app',
    data: {
        count: 0
    },
    methods: {
        increment() {
            this.count++;
        },
        decrement() {
            this.count--;
        }
    },
    created() {
        this.count = 0;  // 閒著沒事寫一下,其實我只是想示範一下 Hooks 在 Vue 裡面咋寫
    },
    mounted() {
        console.log(this.count);
    }
})

mounted() 是示例掛載之後,有點類似傳統 JavaScript 程式設計中的 window.onload。created() 是例項已經建立,但 DOM 還沒渲染的時候,有點類似傳統 JavaScript 程式設計中,把 script 部分程式碼放到 DOM 的上面一樣。見下示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>錯誤示例</title>
</head>
<body>
    <script>
        // 把程式碼放在 DOM 結構前面,程式碼載入的比 DOM 還快,只會導致報錯,因為 DOM 還沒出來程式碼顯出來了
        const counter = document.getElementById('counter');
        const incrementButton = document.getElementById('incrementButton');
        const decrementButton = document.getElementById('decrementButton');
    </script>

    <h1 id="counter">計數器:0</h1>
    <button id="incrementButton">增加</button>
    <button id="decrementButton">減少</button>
</body>
</html>

但總的來說,created() 發生的時機是比 mounted() 早的。除了這幾個 Hooks 以外,Vue 還提供了很多 Hooks 用於開發,具體可參見文件

小結

最後,瞭解完 Vue 的這麼多東西可能資訊量有點爆炸,沒關係,讓我們最後再來看一下傳統 JavaScript 和 Vue 的區別。我們還是以先前的例子計數器為案例,我分別把傳統和 Vue 的兩段程式碼放在一起,用來對比。

<script>
    // 初始化計數器
    let count = 0;
    
    // 獲取 DOM 元素
    const counter = document.getElementById('counter');
    const incrementButton = document.getElementById('incrementButton');
    const decrementButton = document.getElementById('decrementButton');

    // 更新計數器顯示
    function updateCounter() {
        counter.textContent = `計數器:${count}`;
    }

    // 增加計數
    incrementButton.addEventListener('click', () => {
        count++;
        updateCounter();
    });

    // 減少計數
    decrementButton.addEventListener('click', () => {
        count--;
        updateCounter();
    });

    // 初始化顯示
    updateCounter();
</script>
<script>
    new Vue({
        el: '#app',
        data: {
            count: 0
        },
        methods: {
            increment() {
                this.count++;
            },
            decrement() {
                this.count--;
            }
        }
    });
</script>

相比之下,你會發現 Vue 的程式碼相比傳統 JavaScript 更加簡潔、方便。傳統 JavaScript 開發中,我們需要事無鉅細的去操作 DOM,去新增監聽器、修改 textContent,還要控制每一個變數,十分繁雜。而 Vue 恰恰不同,你根本不需要關注具體怎麼操作 DOM,你只需要簡單的寫幾個標籤,裡面放個 {{ count }} 以實現和 data 處的資料繫結,寫幾個放在 methods 裡的方法,就好了,幾行下來就解決了。這也是 Vue 的特點:簡化 DOM 操作資料繫結。專注於開發邏輯的編寫就好了。

再給出一個案例,分別是透過 v-for 指令遍歷陣列內容以渲染表格的。從這裡開始,為了省事,我會直接把 以及 之類的標籤省略掉,不然太多了滑鼠滾的累!

<div id="app">
  <h1>我的任務列表</h1>
  <ul>
    <!-- 使用 v-for 渲染列表 -->
    <li v-for="task in tasks" :key="task.id">
      {{ task.name }}
    </li>
  </ul>
</div>

<script>
  new Vue({
    el: '#app',
    data: {
      // 定義一個簡單的任務列表
      tasks: [
        { id: 1, name: '學習 Vue' },
        { id: 2, name: '完成作業' },
        { id: 3, name: '去超市' }
      ]
    }
  });
</script>

這段程式碼生效後的生成的結果就是:

<ul>
  <li>學習 Vue</li>
  <li>完成作業</li>
  <li>去超市</li>
</ul>

簡單吧。v-for 迴圈 tasks 陣列裡的資訊,如果我們有傳統 JavaScript 就要寫這麼多了:

<ul id="task-list"></ul>

<script>
  // 定義任務陣列
  const tasks = [
    { id: 1, name: '學習 Vue' },
    { id: 2, name: '完成作業' },
    { id: 3, name: '去超市' }
  ];

  // 獲取 ul 元素
  const ul = document.getElementById('task-list');

  // 遍歷任務陣列並建立列表項
  for (const task of tasks) {
    const li = document.createElement('li');
    li.textContent = task.name;
    ul.appendChild(li);
  }
</script>

Vue 最大的意義就是簡化了前端開發和提高開發效率,雖然看起來,Vue 只是簡化了開發人員的操作,但是對普通的使用者而言呢?Vue 的一些特效能讓最終的頁面更流暢(虛擬 DOM),這也涉及到很多的 Vue 的特性,這裡就不多贅述。總之,你甚至根本不需要學習傳統 JavaScript,直接從 Vue 開始都可以,你也不需要再去用一堆 document.querySelector() 之類的去獲取元素、修改了,直接在 HTML 中使用 Vue 的模板語法,就能實現資料繫結和動態更新。當然,如果你沒有一些網站的開發經驗,可能對這些總結無感,如果可以的話,可以去嘗試多寫一些實際的專案,對於自己的未來也是十分有幫助的。

至於更多的,關於 Vue 的功能和語法,你可以查閱 Vue 的官方文件進行學習。到目前為止,本文都是以 Vue 2 為例進行演示。筆者個人也建議,先從 Vue 2 開始學習,有了一定的經驗以後再直接入手 Vue 3 和 React 等進階內容。本文只是引入門,如果把所有東西講一遍那就成文件了(草)。

前端工程化和 Vue 專案

如果有看過 B 站,或者其他地方的一些 Vue 教程,一定離不開建立 Vue 專案。那麼這個專案到底是什麼?我們前面一直是在一個單頁 index.html 中進行演示,那麼所謂的“專案”和我們前面說的,只在一個 .html 頁面內開發又有什麼區別?這就是本章要講的一個重點,前端工程化。

前端怎麼“工程化”?

在上一章中,我們從傳統的 JavaScript,過渡到簡單的 Vue。在我們前面提到的例子中,Vue 是透過<script></script>標籤被引入的,所謂的“Vue”其實也只是一個 JavaScript 檔案。在 Vue 的官方文件中,這種方式被稱為 CDN 引入

所有的程式碼都只在一個 .html 檔案內。如果有別的內容怎麼辦?那就新建一個 .html 檔案,透過一個 a 標籤互相連結,互相跳轉。但是這種辦法會不會太麻煩太不規範了?而且,如果程式碼一多,這個 .html 檔案的大小會越拉越長,看著都累。

那麼,有沒有其它的辦法來更好的來組織程式碼?我們能不能用工程化的方法來改善這些問題?有!前端工程化正是為了解決這些問題而產生的。這也是我們這一章的內容。

當然,我們先不急著直接入手和前端工程化相關的概念,我們講拋棄傳統的 CDN 引入的方式,而是使用各種構建工具,從新建一個 Vue 專案開始。專案不再是隻在一個 index.html 中,而是有了入口 JS 檔案、根元件…… 還有元件、路由、狀態等各種新的概念。讓我們開始吧!

開始

建立專案

  • 配置使用 Vue 2 + Vue CLI。

建立專案的過程可能不是本文主要的,如果有需要可以去其他站點查詢,這裡只能簡單說一下。在你安裝好 Node.js 以後,使用npm install -g @vue/cli安裝腳手架工具,然後透過vue create vue-test-project建立一個 Vue 專案。這裡的 vue-test-project 是專案的名字。之後,我們選擇 Vue 2 + NPM 包管理器,專案會自動下載需要的依賴,配置好後的提示如下。


🎉  Successfully created project vue-test-project.
👉  Get started with the following commands:

 $ cd vue-test-project
 $ npm run serve

隨後,使用npm run serve執行開發伺服器就可以開始編寫了。

 DONE  Compiled successfully in 4643ms                                                                                                                                            20:25:15


  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.31.208:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

在專案建立完了以後,我們來進入資料夾看一下,這個“構建工具”構建了個什麼?

粗略看一下整個專案的結構,會發現為什麼 index.html 跑到了 src/public 下?根目錄下的 main.js 和 src/App.vue 有什麼區別?node_modules 資料夾是什麼?其它的 package.json、vue.config.js 都是什麼東西?彆著急,我們一個一個看。

先從最外層說起。最外層有src資料夾、public資料夾、node_modules資料夾、package.jsonvue.config.js。最前面的 src 資料夾是存放原始碼的,可以說,在專案的開發中,大多數檔案都只在這個資料夾裡面。src 資料夾的內部可以看到有幾個的 .vue 結尾的檔案,這是 Vue 單檔案元件(SFC),還有存放圖片的地方。除此之外,CSS 檔案也是放到 src 目錄下的(雖然這個預設專案沒有 CSS)。

而 public 資料夾就只有一個 index.html 和網站圖示(favicon.ico),這裡面的東西是不會被構建和打包的。欸?!“構建”和“打包”是什麼?

構建和打包過程

構建和打包這兩個雖然聽起來是很高深的詞彙,其實不然。在前端開發中,構建打包是最常見的操作,也就是把我們寫在 src 裡的原始碼轉換成瀏覽器能夠識別的檔案。為什麼要構建和打包?專案檔案不能直接被瀏覽器讀取嗎?答案是肯定的,確實不能。在傳統的開發中,我們只需要寫好一個 index.html 檔案,然後將相關的 JavaScript 和 CSS 檔案直接引入到這個 HTML 檔案中,瀏覽器就能夠載入和渲染這些檔案。

可是,現代的前端開發遠不止於此。需求是會越變越多的,如果接觸過 Java,用過 IDEA 建立過專案,會發現程式碼其實不僅僅只有一個 .java 檔案。給開發人員寫的程式碼和最終匯出的結果是完全不一樣的,這就好比透過 PS 編輯影像,這其中你可能會新建、修改很多圖層,但是最終的結果就只是一張 .png 或者其他格式的圖片。所以,計算機中的專案開發也是同理,工程化是必然的,還是那個結論,寫出來的和最終匯出來的不是一個東西。瀏覽器只能接受 JavaScript 程式碼,不論是什麼框架,他們都需要把最終的東西變成 JS 引擎能執行的程式碼。

此外,不同的瀏覽器對 JS 和 CSS 的支援有所不同,如 IE、Chromium 系(Chrome、Edge)、Firefox、Safari 等。如果依靠手動相容不同平臺,工作量是十分巨大的。但透過構建工具,我們可以將程式碼打包成各種瀏覽器都能理解的格式!對於開發效率的提示是巨大的。

並且,現在的前端在打包過程中都會壓縮圖片、CSS 和其它內容。我們先執行一下構建命令npm run build


> vue-test-project@0.1.0 build
> vue-cli-service build

All browser targets in the browserslist configuration have supported ES module.
Therefore we don't build two separate bundles for differential loading.


⠋  Building for production...

 DONE  Compiled successfully in 11084ms                                                                                                                                           22:14:23

  File                                 Size                                                                    Gzipped

  dist\js\chunk-vendors.b52409db.js    91.36 KiB                                                               32.11 KiB
  dist\js\app.b9180ba3.js              13.11 KiB                                                               8.43 KiB
  dist\css\app.2cf79ad6.css            0.33 KiB                                                                0.23 KiB

  Images and other types of assets omitted.
  Build at: 2024-08-30T14:14:24.033Z - Hash: c6070512a6d23152 - Time: 11084ms

 DONE  Build complete. The dist directory is ready to be deployed.
 INFO  Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html

打包後的檔案會在專案的 dist 資料夾,我們來具體看一下檔案:

打包後會新增一個 dist 資料夾,先不用管一些我們不認識的內容。裡面有一個 js 資料夾,css 資料夾還有一個我們最熟悉的 index.html,又是我們最熟悉的樣子了!專案原始碼被打包一個可以隨便開啟的檔案了!你只需要開啟 index.html,和你在開發時的樣子一模一樣!你可能會擔心 index.html 怎麼匯入 JavaScript 檔案的;Vue 專案裡面所需要的<div id="app"></div>這個裡面也有嗎?我們來看專案程式碼中的 public/index.html:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

構建打包後的內容是這樣的:

<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>vue-test-project</title><script defer="defer" src="/js/chunk-vendors.b52409db.js"></script><script defer="defer" src="/js/app.b9180ba3.js"></script><link href="/css/app.2cf79ad6.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but vue-test-project doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

經過打包後,HTML 內部結構已經被壓縮到一行了。但是我們還是能看出幾個 script 標籤和引入 CSS 的 link 標籤。在執行完npm run build後,負責打包的會自動幫你處理好這些問題,要分幾個 JS 檔案、CSS 檔案,怎麼引入到 index.html 裡面,JS 怎麼再引用 JS 檔案……這都完全不用擔心,它幫你處理好了。在 Vue 裡面打包的工具叫 Webpack,Webpack 是整合在 Vue CLI(腳手架工具)中的,也就是我們在一開始新建 Vue 專案時輸入的指令vue create vue-test-project,這條命令就是 Vue 官方的一個快速構建 Vue 專案的工具 Vue CLI(Vue 3 一般使用 Vite 了)。所以vue.config.js檔案是控制 Vue CLI 的配置檔案。

當然啊!Webpack 打包工具其實是獨立的一個,Vue 只是讓你建立專案更簡單一點,省得你再去安裝 Webpack(npm install webpack)。這種方便你直接開始寫東西的工具被稱為腳手架工具。除了 Vue 框架有這個玩意,React 也有,叫 Create React App。

說回我們之前提到的“而 public 資料夾就只有一個 index.html 和網站圖示(favicon.ico),這裡面的東西是不會被構建和打包的”,此時我們再看打包後的檔案,public 資料夾(index.html 和 favicon.ico)裡的東西和其中是對的上的。

所以,構建和打包是編寫 Vue 專案前必須要理解的一個環節,在上面的篇幅中,我並沒有直接開始講解 Vue 單檔案元件的內容,而是先介紹了前端工程化相關的知識。Vue 開發不一定非得用命令列編譯,它也可以透過 CDN 引入到一個 index.html 裡面。你甚至可以建立一個沒有任何框架和庫的空白工程專案,只需要輸入npm init初始化一個空專案就好了。如果你不嫌麻煩,你可以不用vue create建立專案,而是使用 npm init 之後再一點一點的用npm install vue把 Vue 和其他你想要的包匯入進來。

包和包管理器

啊!讓我們用命令來整理一下剛才說的一堆東西。

npm install -g @vue/cli           # 安裝 Vue CLI,用來更方便的建立 Vue 專案

vue create vue-test-project     # 用 Vue CLI 這個腳手架工具建立 Vue 專案

npm run serve                         # 開發測試專案用的,開啟測試伺服器。前文沒有提到,但是也是非常常用的命令

npm run build                          # 程式碼寫得差不多了,要打包了,就輸入這個命令開始構建。透過 Vue CLI 內部整合的 Webpack 打包工具進行打包~

看完程式碼和總結,相信你大概對構建流程、打包工具和腳手架工具有了一定的瞭解。接下來我們繼續把視角放到npm上。在前面提過的命令中,“npm”是一個被用到了很多次的東西,這是個啥?這叫包管理器,負責安裝各種的。我們前面說的 Webpack(打包工具)、Vue CLI(腳手架工具)都是透過npm install安裝到我們計算機裡的。NPM 彷彿一個應用市場、軟體商店一般,什麼東西都可以被安裝進來。我們現在迴歸正題,繼續觀察 Vue 專案中的檔案,我們來觀察一下和 NPM 相關的 package.json。

{
  "name": "vue-test-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "vue": "^2.6.14"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "vue-template-compiler": "^2.6.14"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "@babel/eslint-parser"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

這個檔案裡面儲存著其實就是專案相關的內容,包括名字name、版本version、執行指令碼scripts、專案依賴dependencies、開發環境的專案依賴devDependencies和各種其他的內容。我只挑指令碼和依賴兩個部分簡單說一下,方便大家理解包之類的東西。

首先是指令碼scripts那個部分。裡面有 build、serve 和 lint。其實,我們執行npm run build就等於執行vue-cli-service build,只是透過 NPM 被簡化了。如果你願意,在 JSON 內新增一個"yuanshen": "vue-cli-service build",以npm run yuanshen來替換npm run build都是可以的。

另外一個是依賴部分。專案依賴 dependencies 是專案被打包後還保留的包,開發環境的專案依賴 devDependencies 是隻在開發環境保留的,比如語法檢測一類的。如果要安裝依賴,透過命令npm install <包名>就可以了,具體的命令語法可以去別的地方查!這裡說太多就寫偏題了。不瞭解“包”的朋友可能會感到困惑,包是什麼?

很簡單,把 NPM 當作軟體商店,“包”就是軟體商店裡面的 APP。開發專案的時候,總不可能全部東西都你自己來寫吧?(666,要是有能力當我沒說)很多時候,為了省事,或多或少都會呼叫一些庫。大名鼎鼎的 UI 庫 Element UI,或者圖示庫,或者是展示 Minecraft 皮膚的庫 skinview3d。很多程式碼不需要自己來寫,只需要引入,呼叫,就可以了!專案資料夾裡的node_modules就是包管理器在這個專案安裝的依賴,全部都放在這個裡面。

當然啊!NPM 可不止能安裝這些東西,像打包工具、腳手架工具都要透過 NPM 來安裝,可以說,沒 NPM 這種包管理器就沒法繼續了!NPM 換句話說其實是 Node.js 的包管理器,但是 Node.js 和本文沒太大關係,就先不說。

除了 NPM 以外,常見的包管理器還有 Yarn 等。

Vue 專案

 DONE  Compiled successfully in 59ms                                                                                                                                              00:39:47


  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.31.208:8080/

在說了那麼多前置知識,終於可以繼續開始我們的 Vue 專案學習了!我們輸入npm run serve之後就可以開啟執行開發伺服器(和構建打包後不同,這個開發環境是給你看的,類似於傳統 JavaScript 程式設計中開啟 index.html 檢視效果),根據 CLI 提示的資訊在瀏覽器內開啟相應地連結即可。

和 CDN 引入方式不同,Vue 專案因為在前端工程化的基礎上,多了許多 CDN 引入沒有的概念。最重要的就是單檔案元件。接下來我們會圍繞單檔案元件結合 Vue 的專案進行解釋。

單檔案元件

讓我們迴歸 Vue 的專案結構,src 目錄下有一個名為 App.vue 的檔案,這是 Vue 的根元件,而 src/components 下又有另一個名為 HelloWorld.vue 的檔案。這些.vue結尾的檔案是 Vue 的單檔案元件。我們開啟 App.vue 觀察一下它的結構。

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

一眼望去,似乎和 HTML 差不多?有些熟悉,又有一些陌生。為什麼存在著三個並立的標籤?<!DOCTYPE html><head>這些基本的標籤去哪了?

最上方的標籤<template></template>被稱為模板,裡面簡單來說,就是寫 HTML 內容的。其他兩個標籤<script></script><style></style>便是 JS 程式碼和 CSS 樣式。這三個標籤共同構成了一個 Vue 元件,如果你願意叫他全稱,你也可以說“單檔案元件”,單檔案元件以 .vue 結尾,你可以把元件理解成 HTML 中的<div></div>,一個 div 裡面可以放其他的 div。同理,你可以在元件裡面放其它的元件,一直巢狀巢狀都可以,只要你願意,你可以把一些重複率很高的程式碼單獨拿出來,單獨抽象成一個元件。這樣也能讓程式碼看得更直觀,看的不累。

而我們拿來舉例子的這個檔案 App.vue 則是 Vue 的根元件,它是最上層的元件,Vue 只會有一個根元件例項,所有其他元件都是作為它的子元件進行巢狀的。在 Vue 應用中,App.vue 作為根元件負責定義應用的總體結構,並且是所有子元件的起點。如果你要寫一些通用的 CSS 樣式,你只需要寫在 App.vue 中的 style 即可。

每個元件之間的 HTML、CSS、JavaScript 都是獨立的,是互不干擾的。在傳統的 JavaScript 開發中,經常容易碰到作用域互相干擾的問題,在 Vue 中則很少碰到這個問題。

JavaScript 和入口檔案

import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}

每個元件的 script 部分和我們第一章提到的 CDN 引入方式的類似。前者是直接把配置物件寫在建構函式內了,CDN 引入這種方式適用於較小的專案或學習示例,但在實際開發中,通常不夠靈活和模組化。而後者的元件裡是透過export default匯出一個配置物件,這種方式使得我們能夠將元件的定義與其他模組分開,從而提高了程式碼的組織性和可維護性。每個元件都可以配置它們獨屬的 data、methods 之類的引數。

exportimport是 JavaScript 的新標準 ES6(ES2015)。export 即匯出,import 即匯入,這些新特性主要用於實現模組化,具體的格式和使用方法可以搜尋一下 ES6 相關的文章。在 Vue 中,這兩個關鍵字是實現元件模組化管理的,在其他元件中匯入另一個元件就是透過import來實現。

這一點可以看 App.vue 是如何匯入 HelloWorld.vue 的。App.vue 在最上方透過import HelloWorld from './components/HelloWorld.vue'匯入元件,隨後在 App.vue 配置物件的 components 引數中引入了這個模組。最後在 template 中透過<HelloWorld msg="Welcome to Your Vue.js App"/>呼叫元件。這裡的msg是 HelloWorld.vue 暴露出來的資料,用於給子元件傳值,父子元件傳值這一點先不急著瞭解,下次再說。接著看被匯入呼叫的元件 src/components/HelloWorld.vue 的 script 部分。

export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}

HelloWorld.vue 透過 export 使得自己可以被 App.vue 匯入,而每一個 Vue 元件又必須有 export。畢竟元件透過 export 匯出一個配置物件,雖然不一定是export default { ... }的形式,也可以是命名匯出,但總之必須 export 出去。import 不是必須的,畢竟如果功能簡單或者其他什麼因素,也不需要用到別的元件。

不過,你可能會問了,那 App.vue 已經是最外一層了,還能被 import 匯入嗎?會,它會被入口檔案呼叫。入口檔案也就是我們先前看專案結構時看到的 main.js 檔案。

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

在 main.js 中,它透過import App from './App.vue'匯入了根元件,最後放到new Vue({ ... })中進行渲染。main.js 的配置物件很簡潔,就一個 render。render 是渲染函式,但這裡不做具體說明,可以參閱官方文件。隨後透過$mount('#app')掛載到 DOM 元素上。另外,這個 JS 入口檔案是全域性性的,App.vue 雖然是根元件,但是隻是在層級上是最頂層,main.js 是整個應用的啟動和配置檔案,確保所有元件的渲染和管理。

最後,import 也用來匯入你裝的其它的包,比如 UI 庫 Element UI 就是需要你透過import ElementUI from 'element-ui'import 'element-ui/lib/theme-chalk/index.css'來匯入到你的專案中的,具體的使用方法每個專案都會說明。並且,呼叫元件不一定非要寫到 export 的外部。像這樣匯入也是可以的(見下),這種方式被稱為動態匯入,也就是允許你在執行時按需載入模組,而不是在應用啟動時立即載入所有模組。如果你直接寫在最外面,那就是靜態匯入,不管你有沒有觸發這個元件裡的內容,都會被立刻載入。

export default {
  name: 'App',
  components: {
    HelloWorld: () => import('./components/HelloWorld.vue')
  }
}

總結一下,在 Vue 中,.vue 檔案是單檔案元件,包含 template、script 和 style 三部分,分別定義元件的模板、邏輯和樣式。App.vue 是應用的根元件,負責整體結構,其他元件透過 import 語法被引入和使用,實現了元件之間的模組化。元件的 script 部分透過 export default 匯出配置物件,定義了元件的功能和資料。這種方式使得元件可以被匯入和組織,從而提高了程式碼的模組化和可維護性。Vue 還支援動態匯入,即在需要時按需載入元件,從而最佳化應用效能和載入速度。

大多數時候說的 “Vue 開發”,指的都是透過 NPM 一類的包管理器建立的 Vue 工程專案,而非 CDN 引入的 Vue。

Vue 3 和 Vue 2 的區別

在文章的最後一個部分,來說一下 Vue 3 和 Vue 2 的區別。Vue 2 其實已經是 2016 年的作品了,2023年末,Vue 2 已經停止被官方停止技術支援了。但是對於新手來說(至少對筆者我來說),Vue 2 相比 Vue 3,會更好上手和入門一些,因此,很多入門教程都是從 Vue 2 開始做起(要從 Vue 3 也可以)。隨後再進入到 Vue 3 的開發,這樣也能更好地理解兩者的不同之處。Vue 3 釋出於 2020 年,距今為止也有四年了。

要說 Vue 3 和 Vue 2 除了效能方面的最佳化和底層的一些改變,最大的改變可能是 Composition API,也被稱作組合式 API。我們以往匯入配置物件,建立 Vue 例項都是透過這樣:

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count += 1;
    }
  }
}
</script>

這種往裡面丟個配置物件的方式被稱為選項式 API。而 Vue 3 的方式是:

<script setup>
import { ref } from 'vue';

const count = ref(0);

const increment = () => {
  count.value += 1;
}
</script>

這種將程式碼一行一行組合起來寫的方式也就是 Vue 3 的新特性之一:組合式 API(Composition API),它有點接近傳統 JavaScript,但是這種方式依舊保留著 Vue 的核心特性(其實這種寫法感覺有點 React)。但這也只是一直風格,Vue 3 的官方文件是可以切換 API 風格的,如果你不習慣這種新的寫法,也可以使用老寫法:選項式 API。

除了組合式 API 以外,響應式系統的變化也很新穎,具體可以去其他文章查詢,就不多說了!

後記

如果這篇文章對你有幫助,我也很高興!如果你大概都瞭解完了這些東西,那麼恭喜你,可以正式開始前端學習了!可以嘗試著去透過 Vue 的官方文件、技術部落格等網站的教程自己學習了。

另外,對於原生 JS 的學習是很重要的,JS 的三座大山:this、原型、非同步;標準庫;閉包、原型鏈、表驅動、最小知識原則、DRY 原則、API 正交原則、過載、鏈式呼叫…… 如果能大致掌握,不管學習什麼框架乃至其他語言都遊刃有餘。

最後,附上一個網上搜集來的前端總結(主要給我自己看的)。

型別 專案名
包管理工具 npm、Yarn 等
打包工具 Webpack、Parcel、Rollup 等
腳手架工具 Create React App、Vue CLI、Angular CLI 等
構建工具 Vite、Snowpack 等
前端框架 React、Angular、Vue.js、Ember.js、Backbone.js 等
狀態管理庫 Redux、MobX、Vuex、Zustand、Recoil 等
前端路由 React Router、Vue Router、Angular Router、Reach Router、Next.js Router 等
UI 庫 Bootstrap、Ant Design、Material UI、Element UI 等
CSS 前處理器 Sass、Less、Stylus 等
程式碼格式化工具 Prettier、ESLint 等
測試工具 Jest、Mocha、Chai 等
模板引擎 Handlebars、Pug、EJS 等
靜態型別檢查器 TypeScript、Flow 等

相關文章