建立我們的第一個 Vue 元件

現在是深入瞭解 Vue 並建立我們自己的自定義元件的時候了——我們將從建立一個元件來表示待辦事項列表中的每個專案開始。在此過程中,我們將學習一些重要的概念,例如在其他元件內部呼叫元件、透過 props 將資料傳遞給它們以及儲存資料狀態。

注意:如果您需要將您的程式碼與我們的版本進行對比,您可以在我們的 todo-vue 程式碼庫 中找到示例 Vue 應用程式碼的完成版本。要檢視執行的即時版本,請訪問 https://mdn.github.io/todo-vue/

先決條件

熟悉核心 HTMLCSSJavaScript 語言,瞭解 終端/命令列

Vue 元件編寫為 JavaScript 物件(管理應用程式的資料)和基於 HTML 的模板語法(對映到底層 DOM 結構)的組合。對於安裝以及使用 Vue 的一些更高階功能(如單檔案元件或渲染函式),您將需要一個安裝了 Nodenpm 的終端。

目標 學習如何建立 Vue 元件、在另一個元件中渲染它、使用 props 將資料傳遞給它以及儲存其狀態。

建立 ToDoItem 元件

讓我們建立我們的第一個元件,它將顯示單個待辦事項。我們將使用它來構建我們的待辦事項列表。

  1. 在您的 moz-todo-vue/src/components 目錄中,建立一個名為 ToDoItem.vue 的新檔案。在您的程式碼編輯器中開啟該檔案。
  2. 透過在檔案頂部新增 <template></template> 來建立元件的模板部分。
  3. 在模板部分下方建立一個 <script></script> 部分。在 <script> 標籤內,新增一個預設匯出的物件 export default {},它是您的元件物件。

您的檔案現在應該如下所示

標記
<template></template>
<script>
export default {};
</script>

我們現在可以開始向我們的 ToDoItem 新增實際內容了。Vue 模板目前只允許一個根元素——一個元素需要包裝模板部分內的所有內容(這將在 Vue 3 釋出時發生變化)。我們將使用 <div> 作為該根元素。

  1. 現在在您的元件模板中新增一個空的 <div>
  2. 在該 <div> 內,讓我們新增一個複選框和一個相應的標籤。向複選框新增一個 id,並新增一個 for 屬性,將複選框對映到標籤,如下所示。
    標記
    <template>
      <div>
        <input type="checkbox" id="todo-item" />
        <label for="todo-item">My Todo Item</label>
      </div>
    </template>
    

在我們的應用程式中使用 TodoItem

這一切都很好,但我們還沒有將元件新增到我們的應用程式中,因此無法測試它並檢視一切是否正常。讓我們現在新增它。

  1. 再次開啟 App.vue
  2. 在您的 <script> 標籤的頂部,新增以下內容以匯入您的 ToDoItem 元件
    js
    import ToDoItem from "./components/ToDoItem.vue";
    
  3. 在您的元件物件中,新增 components 屬性,並在其中新增您的 ToDoItem 元件以註冊它。

您的 <script> 內容現在應該如下所示

js
import ToDoItem from "./components/ToDoItem.vue";

export default {
  name: "app",
  components: {
    ToDoItem,
  },
};

這與 Vue CLI 早期註冊 HelloWorld 元件的方式相同。

要實際在應用程式中渲染 ToDoItem 元件,您需要向上進入您的 <template> 元素並將其作為 <to-do-item></to-do-item> 元素呼叫。請注意,元件檔名及其在 JavaScript 中的表示形式為 PascalCase(例如 ToDoList),而等效的自定義元素為 kebab-case(例如 <to-do-list>)。如果您正在 直接在 DOM 中 編寫 Vue 模板,則必須使用此大小寫樣式。

  1. <h1> 下方,建立一個無序列表(<ul>),其中包含一個列表項(<li>)。
  2. 在列表項內新增 <to-do-item></to-do-item>

您的 App.vue 檔案的 <template> 部分現在應該如下所示

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

如果您再次檢查渲染的應用程式,您現在應該會看到渲染的 ToDoItem,它包含一個複選框和一個標籤。

The current rendering state of the app, which includes a title of To-Do List, and a single checkbox and label

使用 props 使元件動態化

我們的 ToDoItem 元件仍然不是很有用,因為我們只能在一個頁面上包含它一次(ID 必須唯一),並且我們無法設定標籤文字。這方面沒有任何動態性。

我們需要的是一些元件狀態。這可以透過向我們的元件新增 props 來實現。您可以將 props 視為類似於函式中的輸入。prop 的值會為元件提供影響其顯示的初始狀態。

註冊 props

在 Vue 中,有兩種方法可以註冊 props

  • 第一種方法是將 props 作為字串陣列列出。陣列中的每個條目對應於 prop 的名稱。
  • 第二種方法是將 props 定義為物件,每個鍵對應於 prop 名稱。將 props 列出為物件允許您指定預設值、將 props 標記為必需、執行基本物件型別(特別是圍繞 JavaScript 原語型別)以及執行簡單的 prop 驗證。

注意:Prop 驗證僅在開發模式下發生,因此您不能嚴格依賴於生產環境中的驗證。此外,prop 驗證函式在元件例項建立之前呼叫,因此它們無法訪問元件狀態(或其他 props)。

對於此元件,我們將使用物件註冊方法。

  1. 返回您的 ToDoItem.vue 檔案。
  2. 在匯出 default {} 物件內新增一個 props 屬性,其中包含一個空物件。
  3. 在此物件內,新增兩個鍵為 labeldone 的屬性。
  4. label 鍵的值應為具有 2 個屬性(或在元件上下文中稱為 **props**)的物件。
    1. 第一個是 required 屬性,其值為 true。這將告訴 Vue 我們期望此元件的每個例項都具有 label 欄位。如果 ToDoItem 元件沒有 label 欄位,Vue 將會警告我們。
    2. 我們將新增的第二個屬性是 type 屬性。將此屬性的值設定為 JavaScript String 型別(注意大寫“S”)。這告訴 Vue 我們期望此屬性的值為字串。
  5. 現在轉到 done prop。
    1. 首先新增一個 default 欄位,其值為 false。這意味著當沒有 done prop 傳遞給 ToDoItem 元件時,done prop 的值將為 false(請記住,這不是必需的——我們只需要在非必需 prop 上使用 default)。
    2. 接下來新增一個 type 欄位,其值為 Boolean。這告訴 Vue 我們期望值 prop 為 JavaScript 布林型別。

您的元件物件現在應該如下所示

js
export default {
  props: {
    label: { required: true, type: String },
    done: { default: false, type: Boolean },
  },
};

使用註冊的 props

在元件物件中定義了這些 props 後,我們現在可以在我們的模板中使用這些變數值。讓我們首先將 label prop 新增到元件模板中。

在您的 <template> 中,將 <label> 元素的內容替換為 {{label}}

{{}} 是 Vue 中的一種特殊模板語法,它允許我們在模板中列印在我們的類中定義的 JavaScript 表示式的結果,包括值和方法。重要的是要知道 {{}} 內的內容顯示為文字而不是 HTML。在本例中,我們正在列印 label prop 的值。

您的元件的模板部分現在應該如下所示

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

返回您的瀏覽器,您將看到待辦事項像以前一樣渲染,但沒有標籤(哦,不!)。轉到瀏覽器的 DevTools,您將在控制檯中看到類似以下內容的警告

[Vue warn]: Missing required prop: "label"

found in

---> <ToDoItem> at src/components/ToDoItem.vue
        <App> at src/App.vue
          <Root>

這是因為我們將 label 標記為必需 prop,但我們從未向元件提供該 prop——我們已在模板中定義了我們希望使用它的位置,但我們在呼叫它時沒有將其傳遞給元件。讓我們解決這個問題。

在您的 App.vue 檔案中,將 label prop 新增到 <to-do-item></to-do-item> 元件中,就像常規 HTML 屬性一樣

標記
<to-do-item label="My ToDo Item"></to-do-item>

現在您將在應用程式中看到標籤,並且警告不會再次出現在控制檯中。

所以這就是 props 的核心內容。接下來,我們將繼續討論 Vue 如何持久化資料狀態。

Vue 的資料物件

如果您更改傳遞給 App 元件中的 <to-do-item></to-do-item> 呼叫的 label prop 的值,您應該會看到它更新。這很棒。我們有一個複選框,帶有一個可更新的標籤。但是,我們目前沒有對“done”prop 做任何操作——我們可以在 UI 中選中複選框,但在應用程式的任何地方我們都沒有記錄待辦事項是否真正完成。

為了實現這一點,我們希望將元件的 done prop 繫結到 <input> 元素上的 checked 屬性,以便它可以作為複選框是否選中的記錄。但是,重要的是 props 充當單向資料繫結——元件絕不應該更改其自身 props 的值。有很多原因導致這種情況。部分原因是元件編輯 props 會使除錯成為一項挑戰。如果一個值傳遞給多個子元素,則可能難以跟蹤該值的更改來自何處。此外,更改 props 會導致元件重新渲染。因此,在元件中更改 props 將觸發元件重新渲染,這反過來可能會再次觸發更改。

為了解決此問題,我們可以使用 Vue 的 data 屬性管理 done 狀態。data 屬性是您可以在元件中管理本地狀態的地方,它與 props 屬性一起位於元件物件內,並具有以下結構

js
data() {
  return {
    key: value
  }
}

您會注意到 data 屬性是一個函式。這是為了在執行時為元件的每個例項保持資料值唯一——該函式為每個元件例項分別呼叫。如果您將資料宣告為只是一個物件,則該元件的所有例項將共享相同的值。這是 Vue 註冊元件的方式產生的副作用,並且是您不希望發生的事情。

正如您所料,您可以使用 this 從資料內部訪問元件的 props 和其他屬性。我們很快就會看到一個示例。

注意:由於箭頭函式中 this 的工作方式(繫結到父級的上下文),因此如果您使用箭頭函式,則將無法從 data 內部訪問任何必要的屬性。因此,請不要對 data 屬性使用箭頭函式。

因此,讓我們向我們的 ToDoItem 元件新增一個 data 屬性。這將返回一個包含單個屬性的物件,我們將該屬性稱為 isDone,其值為 this.done

像這樣更新元件物件

js
export default {
  props: {
    label: { required: true, type: String },
    done: { default: false, type: Boolean },
  },
  data() {
    return {
      isDone: this.done,
    };
  },
};

Vue 在這裡做了一些魔法——它將所有 props 直接繫結到元件例項,因此我們不必呼叫 this.props.done。它還將其他屬性(data,您已經見過,以及其他屬性,如 methodscomputed 等)直接繫結到例項。部分原因是為了使它們對您的模板可用。這樣做的缺點是您需要使這些屬性的鍵保持唯一。這就是為什麼我們將我們的 data 屬性稱為 isDone 而不是 done 的原因。

因此,現在我們需要將 isDone 屬性附加到我們的元件。與 Vue 使用 {{}} 表示式在模板中顯示 JavaScript 表示式的方式類似,Vue 有一種特殊的語法將 JavaScript 表示式繫結到 HTML 元素和元件:v-bindv-bind 表示式如下所示

v-bind:attribute="expression"

換句話說,您需要在想要繫結的任何屬性/prop 前加上v-bind:。在大多數情況下,您可以使用v-bind屬性的簡寫形式,即在屬性/prop 前加上冒號。因此,:attribute="expression"v-bind:attribute="expression"的效果相同。

因此,在我們ToDoItem元件中的複選框的情況下,我們可以使用v-bindisDone屬性對映到<input>元素上的checked屬性。以下兩種寫法是等價的

標記
<input type="checkbox" id="todo-item" v-bind:checked="isDone" />

<input type="checkbox" id="todo-item" :checked="isDone" />

您可以隨意使用任何一種模式。不過最好保持一致。由於簡寫語法更常用,本教程將堅持使用這種模式。

讓我們來做吧。現在更新您的<input>元素以包含:checked="isDone"

透過將:done="true"傳遞給App.vue中的ToDoItem呼叫來測試您的元件。請注意,您需要使用v-bind語法,否則true將作為字串傳遞。顯示的複選框應被選中。

標記
<template>
  <div id="app">
    <h1>My To-Do List</h1>
    <ul>
      <li>
        <to-do-item label="My ToDo Item" :done="true"></to-do-item>
      </li>
    </ul>
  </div>
</template>

嘗試將true更改為false,然後再改回,並在兩者之間重新載入應用程式,以檢視狀態如何變化。

為 Todos 提供唯一 ID

太棒了!我們現在有一個可以以程式設計方式設定狀態的工作複選框。但是,我們目前只能在頁面上新增一個ToDoList元件,因為id是硬編碼的。這會導致輔助技術的錯誤,因為id需要正確地將標籤對映到它們的複選框。為了解決這個問題,我們可以在元件資料中以程式設計方式設定id

我們可以使用nanoid包來幫助保持索引唯一。此包匯出一個函式nanoid(),該函式生成一個唯一的字串。這足以保持元件id的唯一性。

讓我們使用npm將包新增到我們的專案中;停止您的伺服器並在終端中輸入以下命令

bash
npm install --save nanoid

注意:如果您更喜歡yarn,則可以使用yarn add nanoid

現在我們可以將此包匯入到我們的ToDoItem元件中。在ToDoItem.vue<script>元素頂部新增以下行

js
import { nanoid } from "nanoid";

接下來,在我們的資料屬性中新增一個id欄位,以便元件物件最終看起來像這樣(nanoid()返回一個具有指定字首的唯一字串——todo-

js
import { nanoid } from "nanoid";

export default {
  props: {
    label: { required: true, type: String },
    done: { default: false, type: Boolean },
  },
  data() {
    return {
      isDone: this.done,
      id: "todo-" + nanoid(),
    };
  },
};

接下來,將id繫結到複選框的id屬性和標籤的for屬性,更新現有的idfor屬性,如下所示

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

總結

本文到此結束。此時,我們有一個執行良好的ToDoItem元件,可以傳遞一個要顯示的標籤,將儲存其選中狀態,並且每次呼叫時都會使用唯一的id進行渲染。您可以透過暫時在App.vue中新增更多<to-do-item></to-do-item>呼叫,然後使用瀏覽器的開發者工具檢查其渲染輸出,來檢查唯一的id是否有效。

現在,我們準備向我們的應用程式新增多個ToDoItem元件。在下一篇文章中,我們將介紹如何向我們的App.vue元件新增一組待辦事項資料,然後我們將迴圈遍歷這些資料並在ToDoItem元件中使用v-for指令顯示它們。