Vue 條件渲染:編輯現有待辦事項

現在是時候新增我們仍然缺少的主要功能之一——編輯現有待辦事項的功能。為此,我們將利用 Vue 的條件渲染功能——即v-ifv-else——允許我們在現有待辦事項檢視和編輯檢視之間切換,在編輯檢視中您可以更新待辦事項標籤。我們還將研究新增刪除待辦事項的功能。

先決條件

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

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

目標 學習如何在 Vue 中進行條件渲染。

建立編輯元件

我們可以先建立一個單獨的元件來處理編輯功能。在您的components目錄中,建立一個名為ToDoItemEditForm.vue的新檔案。將以下程式碼複製到該檔案中

html
<template>
  <form class="stack-small" @submit.prevent="onSubmit">
    <div>
      <label class="edit-label">Edit Name for &quot;{{label}}&quot;</label>
      <input
        :id="id"
        type="text"
        autocomplete="off"
        v-model.lazy.trim="newLabel" />
    </div>
    <div class="btn-group">
      <button type="button" class="btn" @click="onCancel">
        Cancel
        <span class="visually-hidden">editing {{label}}</span>
      </button>
      <button type="submit" class="btn btn__primary">
        Save
        <span class="visually-hidden">edit for {{label}}</span>
      </button>
    </div>
  </form>
</template>
<script>
  export default {
    props: {
      label: {
        type: String,
        required: true,
      },
      id: {
        type: String,
        required: true,
      },
    },
    data() {
      return {
        newLabel: this.label,
      };
    },
    methods: {
      onSubmit() {
        if (this.newLabel && this.newLabel !== this.label) {
          this.$emit("item-edited", this.newLabel);
        }
      },
      onCancel() {
        this.$emit("edit-cancelled");
      },
    },
  };
</script>
<style scoped>
  .edit-label {
    font-family: Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    color: #0b0c0c;
    display: block;
    margin-bottom: 5px;
  }
  input {
    display: inline-block;
    margin-top: 0.4rem;
    width: 100%;
    min-height: 4.4rem;
    padding: 0.4rem 0.8rem;
    border: 2px solid #565656;
  }
  form {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
  }
  form > * {
    flex: 0 0 100%;
  }
</style>

注意:請仔細閱讀以上程式碼,然後閱讀下面的描述,確保您在繼續之前理解了元件正在執行的所有操作。這是一種有助於鞏固您迄今為止所學內容的有用方法。

此程式碼設定了編輯功能的核心。我們建立一個帶有<input>欄位的表單,用於編輯待辦事項的名稱。

有一個“儲存”按鈕和一個“取消”按鈕

  • 單擊“儲存”按鈕時,元件透過item-edited事件發出新標籤。
  • 單擊“取消”按鈕時,元件透過發出edit-cancelled事件來表示。

修改我們的 ToDoItem 元件

在將ToDoItemEditForm新增到我們的應用程式之前,我們需要對ToDoItem元件進行一些修改。具體來說,我們需要新增一個變數來跟蹤專案是否正在編輯,以及一個切換該變數的按鈕。我們還將新增一個“刪除”按鈕,因為刪除與之密切相關。

更新您的ToDoItem的模板,如下所示。

html
<template>
  <div class="stack-small">
    <div class="custom-checkbox">
      <input
        type="checkbox"
        class="checkbox"
        :id="id"
        :checked="isDone"
        @change="$emit('checkbox-changed')" />
      <label :for="id" class="checkbox-label">{{label}}</label>
    </div>
    <div class="btn-group">
      <button type="button" class="btn" @click="toggleToItemEditForm">
        Edit <span class="visually-hidden">{{label}}</span>
      </button>
      <button type="button" class="btn btn__danger" @click="deleteToDo">
        Delete <span class="visually-hidden">{{label}}</span>
      </button>
    </div>
  </div>
</template>

我們已在整個模板周圍添加了一個包裝<div>,用於佈局目的。

我們還添加了“編輯”和“刪除”按鈕

  • “編輯”按鈕在單擊時將切換顯示ToDoItemEditForm元件,以便我們可以使用它來編輯我們的待辦事項,透過一個名為toggleToItemEditForm()的事件處理程式函式。此處理程式將isEditing標誌設定為true。為此,我們需要首先在data()屬性中定義它。
  • “刪除”按鈕在單擊時將透過一個名為deleteToDo()的事件處理程式函式刪除待辦事項。在此處理程式中,我們將向父元件發出item-deleted事件,以便可以更新列表。

讓我們定義我們的點選處理程式和必要的isEditing標誌。

在您現有的isDone資料點下方新增isEditing

js
data() {
  return {
    isDone: this.done,
    isEditing: false
  };
}

現在將您的方法新增到methods屬性中,正好位於您的data()屬性下方

js
methods: {
    deleteToDo() {
      this.$emit('item-deleted');
    },
    toggleToItemEditForm() {
      this.isEditing = true;
    }
  }

透過 v-if 和 v-else 有條件地顯示元件

現在我們有一個isEditing標誌,我們可以用它來表示專案正在被編輯(或未被編輯)。如果isEditing為真,我們希望使用該標誌來顯示我們的ToDoItemEditForm而不是複選框。為此,我們將使用另一個 Vue 指令:v-if

v-if指令僅在傳遞給它的值是真值時才渲染塊。這類似於 JavaScript 中if語句的工作方式。v-if也有相應的v-else-ifv-else指令,以在 Vue 模板中提供 JavaScript else ifelse邏輯的等效項。

需要注意的是,v-elsev-else-if塊需要是v-if/v-else-if塊的第一個同級元素,否則 Vue 將無法識別它們。如果需要有條件地渲染整個模板,您還可以將v-if附加到<template>標籤。

最後,您可以在元件的根部使用v-if + v-else來僅顯示一個塊或另一個塊,因為 Vue 每次只會渲染其中一個塊。我們將在我們的應用程式中執行此操作,因為它將允許我們用編輯表單替換顯示待辦事項的程式碼。

首先,將v-if="!isEditing"新增到ToDoItem元件中的根<div>中,

html
<div class="stack-small" v-if="!isEditing"></div>

接下來,在該<div>的結束標籤下方新增以下行

html
<to-do-item-edit-form v-else :id="id" :label="label"></to-do-item-edit-form>

我們還需要匯入和註冊ToDoItemEditForm元件,以便可以在此模板中使用它。在<script>元素的頂部新增此行

js
import ToDoItemEditForm from "./ToDoItemEditForm";

並在元件物件內的props屬性上方新增一個components屬性

js
components: {
  ToDoItemEditForm
},

現在,如果您轉到您的應用程式並單擊待辦事項的“編輯”按鈕,您應該會看到複選框被編輯表單替換。

The todo list app, with Edit and Delete buttons shown, and one of the todos in edit mode, with an edit input and save and cancel buttons shown

但是,目前還沒有辦法返回。為了解決這個問題,我們需要向我們的元件新增更多事件處理程式。

退出編輯模式

首先,我們需要向ToDoItem元件的methods中新增一個itemEdited()方法。此方法應將新專案標籤作為引數,向父元件發出itemEdited事件,並將isEditing設定為false

現在將其新增到您現有方法的下方

js
itemEdited(newLabel) {
  this.$emit('item-edited', newLabel);
  this.isEditing = false;
}

接下來,我們需要一個editCancelled()方法。此方法不帶任何引數,僅用於將isEditing重置為false。在前面的方法下方新增此方法

js
editCancelled() {
  this.isEditing = false;
}

本節的最後,我們將為ToDoItemEditForm元件發出的事件新增事件處理程式,並將相應的方法附加到每個事件。

更新您的<to-do-item-edit-form></to-do-item-edit-form>呼叫,使其如下所示

html
<to-do-item-edit-form
  v-else
  :id="id"
  :label="label"
  @item-edited="itemEdited"
  @edit-cancelled="editCancelled">
</to-do-item-edit-form>

更新和刪除待辦事項

現在我們可以切換編輯表單和複選框。但是,我們實際上還沒有處理在App.vue中更新ToDoItems陣列。為了解決這個問題,我們需要監聽item-edited事件,並相應地更新列表。我們還希望處理刪除事件,以便我們可以刪除待辦事項。

將以下新方法新增到您的App.vue的元件物件中,位於methods屬性中現有方法的下方

js
deleteToDo(toDoId) {
  const itemIndex = this.ToDoItems.findIndex((item) => item.id === toDoId);
  this.ToDoItems.splice(itemIndex, 1);
},
editToDo(toDoId, newLabel) {
  const toDoToEdit = this.ToDoItems.find((item) => item.id === toDoId);
  toDoToEdit.label = newLabel;
}

接下來,我們將新增item-deleteditem-edited事件的事件監聽器

  • 對於item-deleted,您需要將item.id傳遞給該方法。
  • 對於item-edited,您需要傳遞item.id和特殊變數$event。這是一個用於將事件資料傳遞給方法的特殊 Vue 變數。當使用原生 HTML 事件(如click)時,這會將原生事件物件傳遞給您的方法。

更新App.vue模板內的<to-do-item></to-do-item>呼叫,使其如下所示

html
<to-do-item
  :label="item.label"
  :done="item.done"
  :id="item.id"
  @checkbox-changed="updateDoneStatus(item.id)"
  @item-deleted="deleteToDo(item.id)"
  @item-edited="editToDo(item.id, $event)">
</to-do-item>

就是這樣——您現在應該能夠編輯和刪除列表中的專案了!

修復 isDone 狀態的一個小錯誤

到目前為止,這很棒,但我們實際上透過新增編輯功能引入了錯誤。嘗試執行以下操作

  1. 選中(或取消選中)其中一個待辦事項複選框。
  2. 按該待辦事項的“編輯”按鈕。
  3. 按“取消”按鈕取消編輯。

注意取消後複選框的狀態——應用程式不僅忘記了複選框的狀態,而且該待辦事項的已完成狀態現在也出錯了。如果您嘗試再次選中(或取消選中)它,已完成計數將以與您預期相反的方式更改。這是因為data中的isDone僅在元件載入時獲得值this.done

幸運的是,修復此問題非常簡單——我們可以透過將isDone資料項轉換為計算屬性來做到這一點——計算屬性的另一個優點是它們保留了響應性,這意味著(除其他事項外)當模板發生更改時,其狀態會儲存,就像我們現在正在做的那樣。

因此,讓我們在ToDoItem.vue中實現修復

  1. 從我們的data()屬性中刪除以下行
    js
    isDone: this.done,
    
  2. 在data() { }塊下方新增以下塊
    js
    computed: {
      isDone() {
        return this.done;
      }
    },
    

現在,當您儲存並重新載入時,您會發現問題已解決——當您在待辦事項模板之間切換時,複選框狀態現在會保留。

理解事件的交織

最容易混淆的部分之一是我們用來觸發應用程式中所有互動性的標準事件和自定義事件的糾纏。為了更好地理解這一點,最好寫出事件在哪裡發出、在哪裡被監聽以及觸發後會發生什麼的流程圖、描述或圖表。

App.vue

<to-do-form>偵聽

  • 當提交表單時,ToDoForm元件內部的onSubmit()方法發出的todo-added事件。**結果:**呼叫addToDo()方法將新的待辦事項新增到ToDoItems陣列中。

<to-do-item>偵聽

  • 當選中或取消選中時,ToDoItem元件內部的複選框<input>發出的checkbox-changed事件。**結果:**呼叫updateDoneStatus()方法更新關聯待辦事項的已完成狀態。
  • 當按下“刪除”按鈕時,ToDoItem元件內部的deleteToDo()方法發出的item-deleted事件。**結果:**呼叫deleteToDo()方法刪除關聯待辦事項。
  • 當成功監聽ToDoItemEditForm內部的onSubmit()方法發出的item-edited事件時,ToDoItem元件內部的itemEdited()方法發出的item-edited事件。是的,這是一個由兩個不同的item-edited事件組成的鏈!**結果:**呼叫editToDo()方法更新關聯待辦事項的標籤。

ToDoForm.vue

<form>偵聽submit事件。**結果:**呼叫onSubmit()方法,該方法檢查新標籤是否不為空,然後發出todo-added事件(然後在App.vue內部偵聽,請參見上文),最後清除新標籤<input>

ToDoItem.vue

type="checkbox"<input>偵聽change事件。**結果:**當選中/取消選中複選框時發出checkbox-changed事件(然後在App.vue內部偵聽;請參見上文)。

“編輯”<button>偵聽click事件。**結果:**呼叫toggleToItemEditForm()方法,該方法將this.isEditing切換為true,從而在重新渲染時顯示待辦事項的編輯表單。

“刪除”<button>偵聽click事件。**結果:**呼叫deleteToDo()方法,該方法發出item-deleted事件(然後在App.vue內部偵聽;請參見上文)。

<to-do-item-edit-form>偵聽

  • 當成功提交表單時,ToDoItemEditForm元件內部的onSubmit()方法發出的item-edited事件。**結果:**呼叫itemEdited()方法,該方法發出item-edited事件(然後在App.vue內部偵聽,請參見上文),並將this.isEditing重置為false,以便在重新渲染時不再顯示編輯表單。
  • 當單擊“取消”按鈕時,ToDoItemEditForm元件內部的onCancel()方法發出的edit-cancelled事件。**結果:**呼叫editCancelled()方法,該方法將this.isEditing重置為false,以便在重新渲染時不再顯示編輯表單。

ToDoItemEditForm.vue

<form>偵聽submit事件。**結果:**呼叫onSubmit()方法,該方法檢查新標籤值是否為空白,以及是否與舊值相同,如果相同,則發出item-edited事件(然後在ToDoItem.vue內部偵聽,請參見上文)。

"取消" <button> 監聽 click 事件。結果:呼叫 onCancel() 方法,該方法會發出 edit-cancelled 事件(然後在 ToDoItem.vue 內部監聽該事件,請參見上文)。

總結

本文內容相當密集,我們在這裡涵蓋了很多內容。現在,我們的應用程式中有了編輯和刪除功能,這非常令人興奮。我們現在即將結束 Vue 系列課程。要檢視的最後一個功能是焦點管理,或者換句話說,如何改進應用程式的鍵盤可訪問性。