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 系列课程。要查看的最后一个功能是焦点管理,或者换句话说,如何改进应用程序的键盘可访问性。