Vue 中的条件渲染:编辑现有的待办事项
是时候添加一个我们仍然缺少,但十分重要的功能了——编辑现有的待办事项。为此,我们将利用 Vue 的条件渲染(即 v-if
和 v-else
)来让我们能在现有待办视图和用于更新待办的编辑视图之间切换。我们还将实现删除待办事项的功能。
预备条件: |
熟悉核心的 HTML、CSS 和 JavaScript 语言,了解终端/命令行知识。 Vue 组件是由管理应用程序数据的 JavaScript 对象和映射到基础 DOM 结构的基于 HTML 的模板语法组成的。为了进行安装并使用 Vue 的一些更高级的功能(例如“单文件组件(SFC)”或渲染功能),你将需要一个装有 node + npm 的终端。 |
---|---|
目标: | 学习如何使用 Vue 中的条件渲染。 |
创建编辑组件
我们可以先创建一个单独的组件来处理编辑功能。在你的 components
文件夹下,创建一个名为 ToDoItemEditForm.vue
的新文件。复制下面的代码到那个文件:
<template>
<form class="stack-small" @submit.prevent="onSubmit">
<div>
<label class="edit-label">Edit Name for "{{label}}"</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>
标签,用于编辑我们的待办事项的名称。
还有一个“Save”按钮和“Cancel”按钮:
- 当“Save”按钮被点击时,组件通过
item-edited
事件创建一个新标签。 - 当“Cancel”按钮被点击时,组件通过
edit-cancelled
事件发出信号。
修改 ToDoItem 组件
在给我们的应用程序添加 ToDoItemEditForm
之前,我们需要对 ToDoItem
组件做出一些修改。具体来说,我们需要一个变量来监控是否有事项正在被编辑,以及一个按钮来切换那个变量。由于删除和编辑是密切相关的,我们也将添加一个“Delete”按钮。
像下面展示的这样,更新你的 ToDoItem
的模板。
<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>
。
我们还添加了“Edit”和“Delete”按钮:
- 当点击“Edit”按钮时,将切换显示
ToDoItemEditForm
组件,这样我们就可以通过一个名为toggleToItemEditForm()
的事件处理函数,来编辑我们的待办事项。这个处理函数将设置isEditing
标志为真。要做到这一点,我们需要首先在我们的data()
属性中定义它。 - 当点击“Delete”按钮时,将通过名为
deleteToDo()
的事件处理函数删除 todo 项。在这个处理函数中,我们将向父组件发送item-deleted
事件来更新列表。
接下来我们定义点击事件处理函数和必要的 isEditing
标志。
在现有的 isDone
属性下面添加 isEditing
:
data() {
return {
isDone: this.done,
isEditing: false
};
}
现在将你的方法添加到 methods
属性中,放在 data()
属性下面:
methods: {
deleteToDo() {
this.$emit('item-deleted');
},
toggleToItemEditForm() {
this.isEditing = true;
}
}
通过 v-if 和 v-else 有条件地显示组件
现在我们有了一个可用的 isEditing
标志来表示事项正在被编辑(或者未被编辑)。如果 isEditing
为真,我们会用这个标志来展示我们的 ToDoItemEditForm
而不是复选框。为此,我们将使用另一个 Vue 指令:v-if
。
v-if
指令只会在传递给它的值为真的情况下渲染一个块。这和 if
语句在 JavaScript 起作用的方式相似。v-if
也有对应的 v-else-if
和 v-else
指令,在 Vue 模板中提供相当于 JavaScript 的 else if
和 else
逻辑。
需要注意的是,v-else
和 v-else-if
块需要是 v-if
/v-else-if
块的第一个兄弟,否则 Vue 将无法识别它们。如果你需要条件渲染整个模板,你也可以将 v-if
添加到 <template>
标签上。
最后,由于 Vue 只会在一个事件渲染这些块中的一个,你可以在根组件使用 v-if
+ v-else
来只显示一个块。我们将在我们的应用程序中这样做,因为这将使我们能够用编辑表单替换显示待办事项的代码。
首先在 ToDoItem
组件的根 <div>
元素中添加 v-if="!isEditing"
属性,
<div class="stack-small" v-if="!isEditing"></div>
接下来,在该 <div>
的关闭标签下面添加下面这行:
<to-do-item-edit-form v-else :id="id" :label="label"></to-do-item-edit-form>
我们还需要导入和注册 ToDoItemEditForm
组件,这样我才能在这个模板里面使用它。在 <script>
元素的顶部添加这行:
import ToDoItemEditForm from "./ToDoItemEditForm";
在组件对象中 props
属性上方添加一个 components
属性:
components: {
ToDoItemEditForm
},
现在,如果你打开你的应用程序并点击了一个待办事项的“Edit”按钮,你应该可以看到编辑表单代替了复选框。
然而,目前还没有退出编辑的方法。为了解决这个问题,我们需要在组件中添加一些更多的事件处理器。
退出编辑模式
首先,我们需要在 ToDoItem
组件的 methods
里添加一个 itemEdited()
方法。这个方法应该接受新的事项标签作为参数,向父组件发出一个 itemEdited
事件,并将 isEditing
设置为 false
。
现在在你现存的方法下添加它:
itemEdited(newLabel) {
this.$emit('item-edited', newLabel);
this.isEditing = false;
}
接下来,我们需要一个 editCancelled()
方法。这个方法没有参数,只是将 isEditing
设置回 false
。在下面这个方法之前添加这个方法:
editCancelled() {
this.isEditing = false;
}
本节最后,我们将为 ToDoItemEditForm
组件发出的事件添加事件处理程序,并为每个事件添加适当的方法。
像这样更新你的 <to-do-item-edit-form></to-do-item-edit-form>
元素:
<to-do-item-edit-form
v-else
:id="id"
:label="label"
@item-edited="itemEdited"
@edit-cancelled="editCancelled">
</to-do-item-edit-form>
更新和删除 todo 项
现在,我们可以在编辑表单和复选框之间切换了。然而,我们实际上没有处理在 App.vue
中更新 ToDoItems
数组的操作。为了解决这个问题,我们需要监听 item-edited
事件,并相应地更新列表。我们还需要处理删除事件,以便删除待办事项。
在你的 App.vue
组件实体中添加下面新的方法,放在 methods
属性里现存方法下面:
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-deleted
和 item-edited
事件添加事件监听器:
- 对于
item-deleted
,你需要把item.id
传递给该方法。 - 对于
item-edited
,你需要传递item.id
和特殊的$event
变量。这是一个特殊的 Vue 变量,用于传递事件数据给方法。当使用本地 HTML 事件(如click
)时,它将把本地事件对象传递给你的方法。
更新 App.vue
模板内的 <to-do-item></to-do-item>
元素,看起来像这样:
<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
状态的一个小错误
到目前为止似乎还不错,但实际上在添加编辑功能时,我们引入了一个错误。试着这样做:
- 勾选(或取消勾选)任意一个 todo 复选框。
- 按下该待办事项的“Edit”按钮。
- 按“Cancel”按钮取消编辑。
注意你取消后复选框的状态——不仅应用程序忘记了复选框的状态,而且待办事项的完成状态现在也不正常了。如果你尝试再次勾选(或取消勾选)它,完成项的计数值将反向更改。这是因为在组件加载时,data
中的 isDone
只给出了值 this.done
。
幸运的是,修复这个问题很容易:我们可以通过将我们的 isDone
数据转换为计算属性来做到这一点。计算属性的另一个优点是,它们保留了反应性,这意味着(在其他事情中)当模板发生变化时,它们的状态会被保存,就像我们现在做的那样。
所以,让我们来修复一下 ToDoItem.vue
:
-
从
data()
的属性中删除这一行:jsisDone: this.done,
-
在
data() { }
代码块的下面,添加以下内容:jscomputed: { isDone() { return this.done; } },
现在,当你保存并重新加载时,你将发现问题已经解决:当你在 todo 项的模板之间切换时,复选框的状态被保留了。
理解事件的混乱
最可能令人困惑的部分之一是:我们用来触发应用程序中所有交互的事件的混乱。为了更好地理解这一点,最好编写一个流程图、描述或图表,说明是什么事件,在哪里触发和监听,以及触发后会发生什么。
App.vue
<to-do-form>
监听 todo-added
事件,由 ToDoForm
组件内的 onSubmit()
方法在提交表单时触发。结果:addToDo()
方法被调用,用于向 ToDoItems
数组添加新的 todo 项。
<to-do-item>
监听:
checkbox-changed
事件由ToDoItem
组件中的复选框<input>
选中状态变化时触发。结果:updateDoneStatus()
方法被调用来更新相关 todo 项的完成状态。- 当按下“Delete”按钮时,
ToDoItem
组件内的deleteToDo()
方法触发item-deleted
事件。结果:deleteToDo()
方法被调用,来删除相关的 todo 项。 - 当监听到
ToDoItemEditForm
内部的onSubmit()
方法触发的item-edited
事件时,ToDoItem
组件中的itemEdited()
方法触发item-edited
事件。是的,两个不同的item-edited
事件连锁发生了!结果:editToDo()
方法被调用,来更新相关 todo 项的标签。
ToDoForm.vue
<form>
监听 submit
事件。结果:onSubmit()
方法被调用。该方法检查新标签是否为空,然后触发 todo-added
事件(随后 App.vue
中的元素监听到了该事件;见上文),最后清除新标签 <input>
。
ToDoItem.vue
满足type="checkbox"
的 <input>
元素监听 change
事件。结果:当勾选/取消勾选复选框时触发 checkbox-changed
事件(随后 App.vue
中的元素监听到了该事件;见上文)。
“Edit” <button>
监听 click
事件。结果:toggleToItemEditForm()
方法被调用。this.isEditing
变成 true
,在重新渲染时,依次显示 todo 项的编辑表单。
“Delete” <button>
监听 click
事件。结果:deleteToDo()
方法被调用,它会触发 item-deleted
事件(随后 App.vue
中的元素监听到了该事件;见上文)。
<to-do-item-edit-form>
监听:
item-edited
事件:当成功提交表单时,ToDoItemEditForm
组件中的onSubmit()
方法会触发item-edited
事件。结果:itemEdited()
方法被调用,它会触发item-edited
事件(随后App.vue
中的元素监听到了该事件;见上文),并设置this.isEditing
为false
,这样在重新渲染时,编辑的表单将不再显示。edit-cancelled
事件:点击“Cancel”按钮时,ToDoItemEditForm
组件内的onCancel()
方法会触发edit-cancelled
事件。结果:editCancelled()
方法被调用,isEditing
被设置回false
,这样在重新渲染时,编辑的表单将不再显示。
ToDoItemEditForm.vue
<form>
监听 submit
事件。结果:onSubmit()
方法被调用。该方法检查新的标签值是否为空,与旧的标签值是否相同。如果是,则发出 item-edited
事件(随后 ToDoItem.vue
中的元素监听到了该事件;见上文)。
“Cancel” <button>
监听 click
事件。结果:onCancel()
方法被调用。它触发 edit-cancelled
事件(随后 ToDoItem.vue
中的元素监听到了该事件;见上文)。