建立我們的第一個 Vue 元件
現在是深入瞭解 Vue 並建立我們自己的自定義元件的時候了——我們將從建立一個元件來表示待辦事項列表中的每個專案開始。在此過程中,我們將學習一些重要的概念,例如在其他元件內部呼叫元件、透過 props 將資料傳遞給它們以及儲存資料狀態。
注意:如果您需要將您的程式碼與我們的版本進行對比,您可以在我們的 todo-vue 程式碼庫 中找到示例 Vue 應用程式碼的完成版本。要檢視執行的即時版本,請訪問 https://mdn.github.io/todo-vue/。
建立 ToDoItem 元件
讓我們建立我們的第一個元件,它將顯示單個待辦事項。我們將使用它來構建我們的待辦事項列表。
- 在您的
moz-todo-vue/src/components目錄中,建立一個名為ToDoItem.vue的新檔案。在您的程式碼編輯器中開啟該檔案。 - 透過在檔案頂部新增
<template></template>來建立元件的模板部分。 - 在模板部分下方建立一個
<script></script>部分。在<script>標籤內,新增一個預設匯出的物件export default {},它是您的元件物件。
您的檔案現在應該如下所示
<template></template>
<script>
export default {};
</script>
我們現在可以開始向我們的 ToDoItem 新增實際內容了。Vue 模板目前只允許一個根元素——一個元素需要包裝模板部分內的所有內容(這將在 Vue 3 釋出時發生變化)。我們將使用 <div> 作為該根元素。
- 現在在您的元件模板中新增一個空的
<div>。 - 在該
<div>內,讓我們新增一個複選框和一個相應的標籤。向複選框新增一個id,並新增一個for屬性,將複選框對映到標籤,如下所示。標記<template> <div> <input type="checkbox" id="todo-item" /> <label for="todo-item">My Todo Item</label> </div> </template>
在我們的應用程式中使用 TodoItem
這一切都很好,但我們還沒有將元件新增到我們的應用程式中,因此無法測試它並檢視一切是否正常。讓我們現在新增它。
- 再次開啟
App.vue。 - 在您的
<script>標籤的頂部,新增以下內容以匯入您的ToDoItem元件jsimport ToDoItem from "./components/ToDoItem.vue"; - 在您的元件物件中,新增
components屬性,並在其中新增您的ToDoItem元件以註冊它。
您的 <script> 內容現在應該如下所示
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 模板,則必須使用此大小寫樣式。
您的 App.vue 檔案的 <template> 部分現在應該如下所示
<div id="app">
<h1>To-Do List</h1>
<ul>
<li>
<to-do-item></to-do-item>
</li>
</ul>
</div>
如果您再次檢查渲染的應用程式,您現在應該會看到渲染的 ToDoItem,它包含一個複選框和一個標籤。
使用 props 使元件動態化
我們的 ToDoItem 元件仍然不是很有用,因為我們只能在一個頁面上包含它一次(ID 必須唯一),並且我們無法設定標籤文字。這方面沒有任何動態性。
我們需要的是一些元件狀態。這可以透過向我們的元件新增 props 來實現。您可以將 props 視為類似於函式中的輸入。prop 的值會為元件提供影響其顯示的初始狀態。
註冊 props
在 Vue 中,有兩種方法可以註冊 props
- 第一種方法是將 props 作為字串陣列列出。陣列中的每個條目對應於 prop 的名稱。
- 第二種方法是將 props 定義為物件,每個鍵對應於 prop 名稱。將 props 列出為物件允許您指定預設值、將 props 標記為必需、執行基本物件型別(特別是圍繞 JavaScript 原語型別)以及執行簡單的 prop 驗證。
注意:Prop 驗證僅在開發模式下發生,因此您不能嚴格依賴於生產環境中的驗證。此外,prop 驗證函式在元件例項建立之前呼叫,因此它們無法訪問元件狀態(或其他 props)。
對於此元件,我們將使用物件註冊方法。
- 返回您的
ToDoItem.vue檔案。 - 在匯出
default {}物件內新增一個props屬性,其中包含一個空物件。 - 在此物件內,新增兩個鍵為
label和done的屬性。 label鍵的值應為具有 2 個屬性(或在元件上下文中稱為 **props**)的物件。- 第一個是
required屬性,其值為true。這將告訴 Vue 我們期望此元件的每個例項都具有 label 欄位。如果ToDoItem元件沒有 label 欄位,Vue 將會警告我們。 - 我們將新增的第二個屬性是
type屬性。將此屬性的值設定為 JavaScriptString型別(注意大寫“S”)。這告訴 Vue 我們期望此屬性的值為字串。
- 第一個是
- 現在轉到
doneprop。- 首先新增一個
default欄位,其值為false。這意味著當沒有doneprop 傳遞給ToDoItem元件時,doneprop 的值將為 false(請記住,這不是必需的——我們只需要在非必需 prop 上使用default)。 - 接下來新增一個
type欄位,其值為Boolean。這告訴 Vue 我們期望值 prop 為 JavaScript 布林型別。
- 首先新增一個
您的元件物件現在應該如下所示
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 屬性一起位於元件物件內,並具有以下結構
data() {
return {
key: value
}
}
您會注意到 data 屬性是一個函式。這是為了在執行時為元件的每個例項保持資料值唯一——該函式為每個元件例項分別呼叫。如果您將資料宣告為只是一個物件,則該元件的所有例項將共享相同的值。這是 Vue 註冊元件的方式產生的副作用,並且是您不希望發生的事情。
正如您所料,您可以使用 this 從資料內部訪問元件的 props 和其他屬性。我們很快就會看到一個示例。
注意:由於箭頭函式中 this 的工作方式(繫結到父級的上下文),因此如果您使用箭頭函式,則將無法從 data 內部訪問任何必要的屬性。因此,請不要對 data 屬性使用箭頭函式。
因此,讓我們向我們的 ToDoItem 元件新增一個 data 屬性。這將返回一個包含單個屬性的物件,我們將該屬性稱為 isDone,其值為 this.done。
像這樣更新元件物件
export default {
props: {
label: { required: true, type: String },
done: { default: false, type: Boolean },
},
data() {
return {
isDone: this.done,
};
},
};
Vue 在這裡做了一些魔法——它將所有 props 直接繫結到元件例項,因此我們不必呼叫 this.props.done。它還將其他屬性(data,您已經見過,以及其他屬性,如 methods、computed 等)直接繫結到例項。部分原因是為了使它們對您的模板可用。這樣做的缺點是您需要使這些屬性的鍵保持唯一。這就是為什麼我們將我們的 data 屬性稱為 isDone 而不是 done 的原因。
因此,現在我們需要將 isDone 屬性附加到我們的元件。與 Vue 使用 {{}} 表示式在模板中顯示 JavaScript 表示式的方式類似,Vue 有一種特殊的語法將 JavaScript 表示式繫結到 HTML 元素和元件:v-bind。v-bind 表示式如下所示
v-bind:attribute="expression"
換句話說,您需要在想要繫結的任何屬性/prop 前加上v-bind:。在大多數情況下,您可以使用v-bind屬性的簡寫形式,即在屬性/prop 前加上冒號。因此,:attribute="expression"與v-bind:attribute="expression"的效果相同。
因此,在我們ToDoItem元件中的複選框的情況下,我們可以使用v-bind將isDone屬性對映到<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將包新增到我們的專案中;停止您的伺服器並在終端中輸入以下命令
npm install --save nanoid
注意:如果您更喜歡yarn,則可以使用yarn add nanoid。
現在我們可以將此包匯入到我們的ToDoItem元件中。在ToDoItem.vue的<script>元素頂部新增以下行
import { nanoid } from "nanoid";
接下來,在我們的資料屬性中新增一個id欄位,以便元件物件最終看起來像這樣(nanoid()返回一個具有指定字首的唯一字串——todo-)
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屬性,更新現有的id和for屬性,如下所示
<template>
<div>
<input type="checkbox" :id="id" :checked="isDone" />
<label :for="id">{{ label }}</label>
</div>
</template>