Vue學習筆記(四) 久處不厭

北冥有隻魚發表於2023-01-01
本來打算寫JavaFX的,但是比較令人悲傷的是,那一篇部落格沒有同步到GitHub上,還在公司的電腦上。這篇還在。

前言

學習本篇需要有一定的Vue、Node、HTML、JavaScript、CSS基礎,需要一些預置概念。

《Vue學習筆記(一) 初遇篇》介紹Vue的源起以及Vue的基本示例, 《Vue學習筆記(二) 相識篇》對《Vue學習筆記(一) 初遇篇》進行重構,重新組織了《Vue學習筆記(一)初遇篇》的內容: 隨著網頁越來越複雜,開發者們在追求越來越快的開發速度和效能,這也就是現代框架的緣起,相對於JQuery這樣直接操縱DOM結點的庫,導致頁面重繪影響頁面的效能。現代前端框架推出了虛擬DOM不再讓開發者直接操縱DOM結點,同時引入了響應式、模板語法進一步加快開發前端頁面的速度,但是僅僅如此還不夠,現代前端框架開始追求可複用性,這也就是元件,JavaScript、CSS、HTML的複合體,讓我們可以複用邏輯、樣式、功能。

現在我們來回憶一下《Vue學習筆記(三) 》,這篇文章裡面我們講了事件系統、監聽事件、模組化、模組化的好處。以及JavaScript的模組化發展歷史,JavaScript的如何使用模組, 表單的輸入與繫結、元件的簡單使用。但對於Vue的核心概念元件來說我們並未介紹多少,我們只是讓她小小的露了個面,演示了一個元件如何宣告,如何使用。但這種使用元件的方式並不討喜,原因在於這種方式的元件的內容: HTML、CSS、JavaScript都在字串裡面。比較適應的是邏輯、樣式都不復雜的場景,一旦這些內容稍微龐大一點點,龐大的字串就會讓我們頭暈腦脹。

對此的解決方案是單檔案元件,將檔案放在一個檔案中,字尾為.vue。但我們想接著開發前端應用還不大夠,你會發現後面單檔案應用基本上都會選擇用Vue CLI來構建, 那Vue CLI 是什麼? Vue我們是認識的,那CLI呢?首先CLI是Commend-Line Interface的縮寫, Commend-Line Interface 命令列介面,使用者透過鍵盤輸入指令,計算機接收指令後,予以執行。上面是維基百科的定義,下面我們來看下Vue CLI是如何介紹自身的:

Vue CLI 是一個基於 Vue.js 進行快速開發的完整系統,提供:

  • 透過 @vue/cli 實現的互動式的專案腳手架。
  • 透過 @vue/cli + @vue/cli-service-global 實現的零配置原型開發。
  • 一個執行時依賴 (@vue/cli-service),該依賴:

    • 可升級;
    • 基於 webpack 構建,並帶有合理的預設配置;
    • 可以透過專案內的配置檔案進行配置;
    • 可以透過外掛進行擴充套件。
  • 一個豐富的官方外掛集合,整合了前端生態中最好的工具。
  • 一套完全圖形化的建立和管理 Vue.js 專案的使用者介面。

Vue CLI 致力於將 Vue 生態中的工具基礎標準化。它確保了各種構建工具能夠基於智慧的預設配置即可平穩銜接,這樣你可以專注在撰寫應用上,而不必花好幾天去糾結配置的問題。與此同時,它也為每個工具提供了調整配置的靈活性,無需 eject。

我們再來回憶一下《讓我們來來前端工程化》討論了什麼:

隨著瀏覽器頁面的越來越複雜,許多問題變浮現了出來,也許許多事情都是這樣,當問題的規模比較小的時候,通常比較容易解決。一旦問題的規模開始增長,問題的解決難度也隨著上漲。頁面越來越複雜,那就意味著程式碼量也在上升,早期的頁面簡單,JavaScript的設計者沒有為這門語言準備模組化,雖然語言本身沒有自帶,JavaScript社群就自發的為JavaScript設計模組,比較為人所熟知的模組化系統是:

  • AMD —— 最古老的模組系統之一,最初由require.js庫來實現。
  • CommonJS —— 為Node.js伺服器建立的模組系統。
  • UMD —— 另外一個模組系統,建議作為通用的模組系統,它與AMD和CommonJS都相容。

現在 ,它們都在慢慢成為歷史的一部分,但你仍然可以在舊有的專案找到它們,語言級別的模組系統在2015年的時候出現在了標準(ES6)中,此後逐漸發展,現在已經得到了所有主流瀏覽器和Node.js的支援。 模組化解決了在JavaScript如何複用程式碼的問題,從一定程度上來說降低了程式碼量和程式碼複雜度。但這僅僅解決了一個問題,另一個值得注意的問題是,在使用了npm的情況下,最終的頁面該如何交付,開發頁面的時候我們還有CSS、SASS、JPG等各種資源,我們該如何組織這些檔案,WebPack是這個問題的一個答案,WebPack會分析JavaScript之間的依賴關係,將我們專案的檔案進行分類,放到對應的資料夾裡。WebPack這是一個打包工具,它提供的能力還有模組捆綁,所謂模組捆綁指的是將頁面所需的模組捆綁後才能一個大檔案或幾個檔案以減少請求數量。

除此之外,還需要面對的問題是適配低版本瀏覽器的問題(IE瀏覽器走開),有些JavaScript語法好用但是低版本的不支援,但能不能我們只寫一套,由工具幫我們自動產生低版本瀏覽器的程式碼呢? 這也就是Babel。

為了快速高質量的構建專案,我們引入了一個又一個工具,但是該如何快速的搭建起一個專案的模板呢? 我們的願景是這些的透過一些命令列命令就能將webpack、node.js、babel都引入到專案中,並且這些裡面已經寫了一些預設配置,有一個簡單的示例。我只需要寫我需要的頁面就行了。這也就是Vue CLI。

再讀一遍Vue自我介紹的話:

Vue CLI 致力於將 Vue 生態中的工具基礎標準化。它確保了各種構建工具能夠基於智慧的預設配置即可平穩銜接,這樣你可以專注在撰寫應用上,而不必花好幾天去糾結配置的問題。與此同時,它也為每個工具提供了調整配置的靈活性,無需 eject。

開啟第一個單檔案專案吧

本專案首先需要一個node環境,要求Node的版本在14.18.1以上,雖然Vue 的最新大版本已經到了3.2了,但我們這裡的Vue大版本選擇還是2.5.2。後面會專門再研究3和2的區別。我們本次藉助Vue CLI來構建專案,我們藉助VS Code來編寫程式碼,我的VS Code安裝的外掛有:

  • vue-helper: Vue程式碼提示和元件跳轉。

第一步開啟VS Code, 點選終端, 在終端中輸入命令:

# 這個命令代表的意思是全域性安裝vue cli
npm install --global @vue/cli

然後在終端裡面輸入:

# Vue 腳手架就會幫我們建立一個叫my-project的專案
# 在建立過程中會給你一些選擇,比如輸入專案描述啊之類的
vue init webpack my-project

專案結構

最終的專案結構如下圖所示:

Vue專案結構

  • .eslintrc.js:程式碼檢測工具, 統一程式碼風格
  • .babellrc: 可以在JavaScript使用新特性,並在生產環境將其轉換為可以跨瀏覽器執行的舊程式碼,也可以在裡面配置babel外掛。
  • package.json: npm的配置檔案,在這裡我們可以引用第三方JavaScript庫。
  • .postcssrc.js: 可以使用CSS中使用新特性,並在生產環境將其轉換為可以跨瀏覽器執行的程式碼。
  • src: 這個是Vue應用的核心目錄

    • main.js:這是應用的入口檔案。目前它會初始化應用並且指定將應用掛在到index.html檔案中的哪個HTML元素上。通常還會做一些註冊全域性元件或者新增額外的Vue庫的操作。
    • App.vue: 這是Vue應用的根結點元件,目前裡面會有一個示例元件。
    • assets: 這個目錄用來存放像CSS、圖片這種靜態資源。但是他們放在程式碼目錄下面,所以可以使用webpack來操作和處理。

      ## 執行Vue專案

之前我用Vue寫的頁面都是在html裡面,我們直接用瀏覽器開啟即可。那現在的專案我們該如何看頁面效果呢?我們在終端裡輸入npm run dev 即可, 像下面的圖一樣:

如何執行專案

本質上執行的還是

webpack-dev-server --inline --progress --config build/webpack.dev.conf.js

然後在控制檯就可以看到:

專案執行之後的結果

然後我們在瀏覽器訪問這個地址:

瀏覽器效果圖

開啟第一個元件

先來複習一下什麼是元件,在Vue 3的中文文件倒是沒給出定義, 只是簡單論述了一下:

元件允許我們將 UI 劃分為獨立的、可重用的部分,並且可以對每個部分進行單獨的思考。

好像說了什麼,又好像什麼都沒說,既然Vue的中文文件沒給出元件的定義(也可能是我沒看到),那我們這裡就嘗試推敲出一個定義, 在網頁中我們通常並不止使用HTML,我們用html搭建基本的內容骨架,用CSS對內容骨架進行美化,用JavaScript為實現業務邏輯和頁面控制。當我們想要複用的時候通常是三者都想複用,而不僅僅是複用其中一項,基於此我們嘗試給出元件的定義就是:

一種包含內容(HTML)、樣式(CSS)、邏輯(JavaScript)的組合體。

我們來直觀感受一下元件, 在我們剛才建立的專案中就有一個元件,在src/components下面,它叫HelloWorld.vue, 裡面的內容如下所示:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h2>Essential Links</h2>
    <ul>
      <li>
        <a
          href="https://vuejs.org"
          target="_blank"
        >
          Core Docs
        </a>
      </li>
      <li>
        <a
          href="https://forum.vuejs.org"
          target="_blank"
        >
          Forum
        </a>
      </li>
      <li>
        <a
          href="https://chat.vuejs.org"
          target="_blank"
        >
          Community Chat
        </a>
      </li>
      <li>
        <a
          href="https://twitter.com/vuejs"
          target="_blank"
        >
          Twitter
        </a>
      </li>
      <br>
      <li>
        <a
          href="http://vuejs-templates.github.io/webpack/"
          target="_blank"
        >
          Docs for This Template
        </a>
      </li>
    </ul>
    <h2>Ecosystem</h2>
    <ul>
      <li>
        <a
          href="http://router.vuejs.org/"
          target="_blank"
        >
          vue-router
        </a>
      </li>
      <li>
        <a
          href="http://vuex.vuejs.org/"
          target="_blank"
        >
          vuex
        </a>
      </li>
      <li>
        <a
          href="http://vue-loader.vuejs.org/"
          target="_blank"
        >
          vue-loader
        </a>
      </li>
      <li>
        <a
          href="https://github.com/vuejs/awesome-vue"
          target="_blank"
        >
          awesome-vue
        </a>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

我們來簡單的看一下這個元件的組成:

  • template(html內容)
  • script(JavaScript行為): 在這個元件中, 我們只匯出了該元件,名字是helloWorld。注意到之前我們寫的示例data是一個物件,現在data是一個函式而且必須是一個函式,這樣各個元件就能夠互不影響。
  • style: 樣式

那我們該如何使用這個元件呢,首先我們App.vue中引入該元件, 然後再註冊:

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

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

然後我們在App.vue中直接將這個元件名當標籤一樣使用即可,像下面這樣:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view/>
    <hello-world/>
    <hello-world/>
  </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>

下面是瀏覽器中的效果:

對映關係

就像很多其他的前端框架一樣,元件是構建Vue應用中非常重要的一部分。元件可以把一個很大的應用程式拆分成獨立建立和管理的不相關區塊,然後彼此按需傳遞資料,這些小的程式碼塊可以方便更容易的理解和測試。

Vue把模板、相關指令碼和CSS一起整合放在.vue結尾的一個單檔案中。這些檔案最終會透過JavaScript(後面統稱為JS)打包工具(如Webpack)處理,這意味著你可以使用構建工具來完成更多複雜的元件,比如Babel、TypeScript、SCSS。

待辦表單元件

注意上面我們的一句話:

元件可以把一個很大的應用程式拆分成獨立建立和管理的不相關區塊,然後彼此按需傳遞資料,這些小的程式碼塊可以更方便更容易理解和測試。

這其實在說的是, 我們將應用拆分為若干元件,那元件之間如何傳遞資料,我們將在下面的例子中演示,這個例子來自MDN Web docs,地址是:

https://developer.mozilla.org...

後面的介紹也多基於這個例子,只不過會加入一點我個人的理解。剛才那個元件來自Vue CLI,現在讓我們自己建立一個元件,首先我們在src/components目錄下建立一個ToDoItem.vue。然後再這個檔案下建立三個標籤: template、script、style。像下面一樣:

<template>
  
</template>

<script>
export default {

}
</script>

<style>

</style>

現在讓我們來豐富這個元件的內容:

<template>
  <div>
     <input type="checkbox" id="todo-item" checked="false"/>
     <label for="todo-item"> 我的待辦</label>
  </div>
</template>

<script>
export default {
  name: 'ToDoItem'
}
</script>

<style>

</style>

然後我們在App.vue中使用這個元件,要使用一個元件,是固定的步驟,第一步引入這個元件,第二步註冊這個元件:

<template>
  <div id="app">
    <h1>To-Do List</h1>
  <ul>
    <li>
      <to-do-item></to-do-item>
    </li>
  </ul>
  </div>
</template>

<script>
import ToDoItem from './components/ToDoItem.vue'
export default {
  name: 'App',
  components: {
    ToDoItem
  }
}
</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規範,ID必須唯一,我們只能在頁面上包含一次。但由於瀏覽器的寬容,你會發現包含兩次也照樣能渲染出來。除此之外,label標籤的內容是固定的,這不是動態的。那如何讓這個元件聰明起來呢,這也就是props

props

在Vue中,註冊props的方式有兩種:

  • 以字串陣列的方式列出props,陣列中的每個實體對應一個prop名稱。
Vue.component('my-component',{
    // 宣告 props
    props:['message'],
    // 在模板中使用繫結語法來繫結到prop
template: '<div>{{message}}</div>'
}) 
  • 將props定義為一個物件,每個key對應於prop名稱。將props列為物件可以指定預設值,將props標記為required,執行基本的物件型別(特別是JavaScript基本型別),並執行簡單的prop校驗。
注意:prop 驗證只能在 development 模式下進行,所以你不能在生產環境中嚴格依賴它。此外,prop 驗證函式在元件例項建立之前被呼叫,因此它們不能訪問元件狀態 (或其他 props)
Vue.component('my-component', {
  // 宣告 props
  props: {
    message: {
      type: String,
      default: 'Hello'
    }
  },
  template: '<div>{{ message }}</div>'
})

對於針對 ToDoItem 元件,我們將使用物件註冊法,我們在預設匯出的物件{}物件上新增一個props物件,該props屬性含有一個空物件。在這個物件裡面我們新增兩個key為label和done屬性。label的值應該是一個帶有兩個屬性的物件,第一個是required屬性,如果它的值是true。那麼這就相當於告訴Vue說,每個該元件的例項必須有一個label欄位。如果沒有,Vue提示會給出警告。第二個是新增一個type屬性,如果它的值是String型別,這等於在告訴Vue,我們希望type屬性的值是String型別。

現在轉向done prop, 首先新增一個default屬性,預設值是false,這意味著當沒有給ToDoItem元件傳遞done prop,done prop的值會是false。default 不是必需的,我們只在非required裡才需要default。然後新增一個type屬性,值為Boolean,這將告訴Vue,我們希望這個Prop的值是JavaScript的Boolean型別。現在ToDoItem的script變成了下面這樣:

<script>
export default {
  name: 'ToDoItem',
  props: {
    label: {required: true, type: String},
    done: {default: false, type: Boolean}
  }
}
</script>

現在元件宣告瞭它所需要的值,我們就可以在template裡面使用這些變數值,像下面這樣:

<template>
  <div>
     <input type="checkbox" id="todo-item" checked="false"/>
     <label for="todo-item"> {{label}}</label>
  </div>
</template>

再次執行專案,你會發現瀏覽器的控制檯會輸出警告:

[Vue warn]: Missing required prop: "label"
found in
---> <ToDoItem> at src/components/ToDoItem.vue
       <App> at src/App.vue
         <Root>

這是因為我們在ToDoItem將label prop標記為必傳,但是我們在使用這個元件的時候卻沒有傳入。現在讓我們來修復這個問題,在使用這個元件的實驗傳遞進去:

<template>
  <div id="app">
    <h1>To-Do List</h1>
  <ul>
    <li>
      <to-do-item label="hello prop" done="true"></to-do-item>
    </li>
  </ul>
  </div>
</template>

<script>
import ToDoItem from './components/ToDoItem.vue'
export default {
  name: 'App',
  components: {
    ToDoItem
  }
}
</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的標籤的id不允許重複,如果你重複了,沒問題的原因在於瀏覽器比較寬容。現在我們來嘗試為label的for和input的id產生唯一值,在專案中我們用到了npm,這意味著我們可以大量使用第三方庫,我們這裡用來產生唯一id的庫叫lodash,首先我們在終端裡面嘗試安裝它:

npm install --save lodash.uniqueid

然後在script標籤裡面引入:

import uniqueId from 'lodash.uniqueid';

然後在data裡面宣告一個id,並將data的id和核取方塊的id、標籤的for屬性進行繫結,如下所示:

<template>
  <div>
     <input type="checkbox" :id="id" :checked="isDone"/>
     <label :for="id"> {{label}}</label>
  </div>
</template>

<script>
import uniqueId from 'lodash.uniqueid'
export default {
  name: 'ToDoItem',
  props: {
    label: {required: true, type: String},
    done: {default: false, type: Boolean}
  },
  data () {
    return {
      isDone: this.done,
      id: uniqueId('todo-')
    }
  }
}
</script>

<style>

</style>

然後開啟瀏覽器觀察原始碼為發現,label的for和核取方塊的id都產生了唯一鍵。

總結一下

在《讓我們來來前端工程化》我們簡單討論了前端工程化,工程化的一個標誌就是引入越來越多的工具,而Vue CLI則將這些工具一站式整合,透過一行命令我們就能輕易的整合這些工具,簡單的搭建一個前端工程。元件可以把一個很大的應用程式拆分成獨立建立和管理的不相關區塊,然後彼此按需傳遞資料,這些小的程式碼塊可以方便更容易的理解和測試。當我們將應用程式拆分,那隨之伴隨而來的一個問題就是元件之間該如何通訊,這裡我們透過一個小的待辦元件,介紹了父子元件之間傳遞資料的方式,也就是props。

參考資料

相關文章