添加新的待辦事項表單:Vue 事件、方法和模型

现在我们已经有了示例数据,以及一个循环,它获取每个数据并将其渲染到应用中的 ToDoItem 中。我们接下来真正需要的是让用户能够将他们自己的待办事项输入到应用程序中,为此我们需要一个文本 <input>,一个当数据提交时触发的事件,一个在提交时触发的添加数据并重新渲染列表的方法,以及一个控制数据的模型。这就是我们将在本文中讨论的内容。

先决条件

熟悉核心 HTMLCSSJavaScript 语言,了解 终端/命令行

Vue 组件以 JavaScript 对象(管理应用程序数据)和基于 HTML 的模板语法(映射到底层 DOM 结构)的组合形式编写。要进行安装,并使用 Vue 的一些更高级功能(如单文件组件或渲染函数),您需要一个安装了 node + npm 的终端。

目标 了解如何在 Vue 中处理表单,以及相关的事件、模型和方法。

創建新的待辦事項表單

我们现在有一个显示待办事项列表的应用程序。但是,我们无法更新项目列表,除非手动更改代码!让我们解决这个问题。让我们创建一个新的组件,允许我们添加新的待办事项。

  1. 在您的组件文件夹中,创建一个名为 ToDoForm.vue 的新文件。
  2. 添加一个空白的 <template> 和一个 <script> 标签,就像以前一样。
    html
    <template></template>
    
    <script>
      export default {};
    </script>
    
  3. 让我们添加一个 HTML 表单,它允许您输入新的待办事项并将其提交到应用程序中。我们需要一个 <form>,带有一个 <label>、一个 <input> 和一个 <button>。更新您的模板,如下所示。
    html
    <template>
      <form>
        <label for="new-todo-input"> What needs to be done? </label>
        <input
          type="text"
          id="new-todo-input"
          name="new-todo"
          autocomplete="off" />
        <button type="submit">Add</button>
      </form>
    </template>
    
    因此,我们现在有一个表单组件,可以在其中输入新的待办事项标题(当它最终被渲染时,它将成为相应 ToDoItem 的标签)。
  4. 让我们将此组件加载到我们的应用程序中。返回到 App.vue,并在之前的语句下方添加以下 import 语句,位于您的 <script> 元素中。
    js
    import ToDoForm from "./components/ToDoForm";
    
  5. 您还需要在 App 组件中注册新的组件 — 更新组件对象的 components 属性,使其看起来像这样。
    js
    components: {
      ToDoItem, ToDoForm,
    }
    
  6. 最后,在本节中,通过将 <to-do-form /> 元素添加到 App<template> 中,将 ToDoForm 组件渲染到应用程序中,如下所示。
    html
    <template>
      <div id="app">
        <h1>My To-Do List</h1>
        <to-do-form></to-do-form>
        <ul>
          <li v-for="item in ToDoItems" :key="item.id">
            <to-do-item
              :label="item.label"
              :done="item.done"
              :id="item.id"></to-do-item>
          </li>
        </ul>
      </div>
    </template>
    

现在,当您查看运行的网站时,您应该会看到新显示的表单。

Our todo list app rendered with a text input to enter new todos

如果您填写它并点击“添加”按钮,该页面将把表单发布回服务器,但这并不是我们真正想要的。我们实际上想要做的是在 submit 事件 上运行一个方法,该方法将新的待办事项添加到 App 内定义的 ToDoItem 数据列表中。为此,我们需要向组件实例添加一个方法。

創建方法並使用 v-on 將其綁定到事件

要使一个方法对 ToDoForm 组件可用,我们需要将其添加到组件对象中,这在组件对象的 methods 属性中完成,它位于 data()props 等的位置。methods 属性保存我们可能需要在组件中调用的任何方法。当引用时,方法会完全运行,因此不要将其用于在模板中显示信息。对于显示来自计算的数据,您应该使用 computed 属性,我们将在稍后介绍。

  1. 在此组件中,我们需要向 ToDoForm 组件对象的 methods 属性中添加一个 onSubmit() 方法。我们将使用它来处理提交操作。按如下方式添加它。
    js
    export default {
      methods: {
        onSubmit() {
          console.log("form submitted");
        },
      },
    };
    
  2. 接下来,我们需要将该方法绑定到 <form> 元素的 submit 事件处理程序。与 Vue 如何使用 v-bind 语法绑定属性非常类似,Vue 还有一个用于事件处理的特殊指令:v-onv-on 指令通过 v-on:event="method" 语法工作。并且与 v-bind 类似,也有一种简写语法:@event="method"。为了保持一致性,我们将在这里使用简写语法。将 submit 处理程序添加到您的 <form> 元素中,如下所示。
    html
    <form @submit="onSubmit"></form>
    
  3. 当您运行它时,应用程序仍然将数据发布到服务器,导致刷新。由于我们是在客户端执行所有处理,因此没有服务器来处理回发。我们还会在页面刷新时丢失所有本地状态。要防止浏览器发布到服务器,我们需要在页面冒泡时停止事件的默认操作(Event.preventDefault(),在普通 JavaScript 中)。Vue 有一种称为 **事件修饰符** 的特殊语法,可以为我们直接在模板中处理这个问题。修饰符以点为后缀附加到事件的末尾,如下所示:@event.modifier。以下是事件修饰符列表。
    • .stop:停止事件传播。等效于普通 JavaScript 事件中的 Event.stopPropagation()
    • .prevent:防止事件的默认行为。等效于 Event.preventDefault()
    • .self:仅当事件从该确切元素发出时才触发处理程序。
    • {.key}:仅通过指定的键触发事件处理程序。MDN 有一个有效的键值列表;多词键只需要转换为 kebab-case(例如 page-down)。
    • .native:侦听组件的根(最外层包装)元素上的本机事件。
    • .once:侦听事件,直到它被触发一次,之后不再侦听。
    • .left:仅通过左鼠标按钮事件触发处理程序。
    • .right:仅通过右鼠标按钮事件触发处理程序。
    • .middle:仅通过中间鼠标按钮事件触发处理程序。
    • .passive:等效于在使用 addEventListener() 的普通 JavaScript 中创建事件侦听器时使用 { passive: true } 参数。
    在这种情况下,我们需要使用 .prevent 修饰符来停止浏览器的默认提交操作。在您的模板中,将 .prevent 添加到 @submit 处理程序中,如下所示。
    html
    <form @submit.prevent="onSubmit"></form>
    

如果您现在尝试提交表单,您会注意到页面没有重新加载。如果打开控制台,您可以看到我们在 onSubmit() 方法中添加的 console.log() 的结果。

使用 v-model 將數據綁定到輸入

接下来,我们需要一种方法来获取表单的 <input> 的值,以便我们可以将新的待办事项添加到 ToDoItems 数据列表中。

我们首先需要在表单中有一个 data 属性来跟踪待办事项的值。

  1. ToDoForm 组件对象添加一个 data() 方法,该方法返回一个 label 字段。我们可以将 label 的初始值设置为一个空字符串。您的组件对象现在应该看起来像这样。
    js
    export default {
      methods: {
        onSubmit() {
          console.log("form submitted");
        },
      },
      data() {
        return {
          label: "",
        };
      },
    };
    
  2. 我们现在需要一种方法将 new-todo-input 元素的字段的值附加到 label 字段。Vue 专门为此提供了一个指令:v-modelv-model 绑定到您为其设置的数据属性,并使其与 <input> 保持同步。v-model 在所有各种输入类型中都适用,包括复选框、单选按钮和选择输入。要使用 v-model,您需要向 <input> 添加一个具有 v-model="variable" 结构的属性。因此,在我们的案例中,我们将将其添加到 new-todo-input 字段中,如下所示。现在就执行此操作。
    html
    <input
      type="text"
      id="new-todo-input"
      name="new-todo"
      autocomplete="off"
      v-model="label" />
    

    注意:您还可以通过事件和 v-bind 属性的组合来同步数据与 <input> 值。事实上,这就是 v-model 在幕后所做的。但是,确切的事件和属性组合因输入类型而异,并且需要比仅仅使用 v-model 快捷方式更多的代码。

  3. 让我们通过在 onSubmit() 方法中记录提交的数据的值来测试 v-model 的使用。在组件中,数据属性使用 this 关键字访问。因此,我们使用 this.label 访问 label 字段。更新您的 onSubmit() 方法,使其看起来像这样。
    js
    methods: {
      onSubmit() {
        console.log('Label value: ', this.label);
      }
    },
    
  4. 现在返回运行的应用程序,在 <input> 字段中添加一些文本,然后点击“添加”按钮。您应该会在控制台中看到您输入的值,例如
    Label value: My value
    

使用修飾符更改 v-model 行為

与事件修饰符类似,我们也可以添加修饰符来更改 v-model 的行为。在我们的案例中,有两个值得考虑。第一个是 .trim,它将删除输入之前或之后的空格。我们可以将修饰符添加到 v-model 语句中,如下所示:v-model.trim="label"

我们应该考虑的第二个修饰符称为 .lazy。此修饰符更改了 v-model 为文本输入同步值的时间。如前所述,v-model 同步通过使用事件来更新变量。对于文本输入,这是使用 input 事件 完成的。通常,这意味着 Vue 会在每次按键后同步数据。.lazy 修饰符会导致 v-model 使用 change 事件。这意味着 Vue 只有在输入失去焦点或提交表单时才会同步数据。对于我们的目的,这是更合理的,因为我们只需要最终数据。

要同时使用 .lazy 修饰符和 .trim 修饰符,我们可以将它们链接起来,例如 v-model.lazy.trim="label"

更新您的 v-model 属性以链接 lazytrim,如上所示,然后再次测试您的应用程序。例如,尝试提交一个两端都有空格的值。

使用自定義事件將數據傳遞給父級

我们现在非常接近能够将新的待办事项添加到我们的列表中。接下来,我们需要能够将新创建的待办事项传递给 App 组件。为此,我们可以让 ToDoForm 发出一个传递数据的自定义事件,并让 App 监听它。这与 HTML 元素上的本机事件非常相似:子组件可以发出一个事件,该事件可以通过 v-on 监听。

在我们的 ToDoFormonSubmit 事件处理程序中,让我们添加一个 todo-added 事件。自定义事件的发出方式如下:this.$emit("event-name")。重要的是要知道,事件处理程序区分大小写,并且不能包含空格。Vue 模板也会转换为小写,这意味着 Vue 模板无法监听以大写字母命名的事件。

  1. onSubmit() 方法中的 console.log() 替换为以下内容
    js
    this.$emit("todo-added");
    
  2. 接下来,返回 App.vue 并将一个 methods 属性添加到你的组件对象中,其中包含一个 addToDo() 方法,如下所示。目前,这个方法只需将 To-do added 日志记录到控制台中。
    js
    export default {
      name: "app",
      components: {
        ToDoItem,
        ToDoForm,
      },
      data() {
        return {
          ToDoItems: [
            { id: "todo-" + nanoid(), label: "Learn Vue", done: false },
            {
              id: "todo-" + nanoid(),
              label: "Create a Vue project with the CLI",
              done: true,
            },
            { id: "todo-" + nanoid(), label: "Have fun", done: true },
            {
              id: "todo-" + nanoid(),
              label: "Create a to-do list",
              done: false,
            },
          ],
        };
      },
      methods: {
        addToDo() {
          console.log("To-do added");
        },
      },
    };
    
  3. 接下来,将一个针对 todo-added 事件的事件监听器添加到 <to-do-form></to-do-form> 中,当事件触发时,它会调用 addToDo() 方法。使用 @ 简写,监听器看起来像这样:@todo-added="addToDo"
    html
    <to-do-form @todo-added="addToDo"></to-do-form>
    
  4. 当你提交 ToDoForm 时,你应该会看到 addToDo() 方法中的控制台日志。这很好,但我们还没有将任何数据传回 App.vue 组件。我们可以通过在 ToDoForm 组件中的 this.$emit() 函数中传递额外的参数来做到这一点。在本例中,当我们触发事件时,我们希望将 label 数据一并传递过去。这可以通过将你想要传递的数据作为 $emit() 方法中的另一个参数来实现:this.$emit("todo-added", this.label)。这类似于原生 JavaScript 事件包含数据的方式,只是自定义 Vue 事件默认情况下不包含任何事件对象。这意味着发出的事件将直接与你提交的任何对象匹配。因此,在我们的例子中,我们的事件对象只是一个字符串。更新你的 onSubmit() 方法,如下所示
    js
    onSubmit() {
      this.$emit('todo-added', this.label)
    }
    
  5. 为了实际在 App.vue 中获取此数据,我们需要在 addToDo() 方法中添加一个参数,该参数包含新待办事项的 label。返回 App.vue 并立即更新它
    js
    methods: {
      addToDo(toDoLabel) {
        console.log('To-do added:', toDoLabel);
      }
    }
    

如果你再次测试你的表单,你将看到你在提交时在控制台中记录的任何文本。Vue 自动将 this.$emit() 中事件名称之后的参数传递到你的事件处理程序。

將新的待辦事項添加到我們的數據中

现在我们已经获得了 ToDoForm 中的数据,并将其提供给 App.vue,我们需要向 ToDoItems 数组中添加一个代表它的项目。这可以通过将一个包含我们新数据的待办事项对象推送到数组中来实现。

  1. 更新你的 addToDo() 方法,如下所示
    js
    addToDo(toDoLabel) {
      this.ToDoItems.push({id: "todo-" + nanoid(), label: toDoLabel, done: false});
    }
    
  2. 再次尝试测试你的表单,你应该会看到新的待办事项被追加到列表的末尾。
  3. 在我们继续之前,让我们再做进一步的改进。如果你在输入为空时提交表单,则仍然会将没有文本的待办事项添加到列表中。为了解决这个问题,我们可以防止在名称为空时触发 todo-added 事件。由于名称已经被 .trim 修饰符修剪过,我们只需要测试空字符串。返回你的 ToDoForm 组件,并更新 onSubmit() 方法,如下所示。如果标签值为空,则不要发出 todo-added 事件。
    js
    onSubmit() {
      if (this.label === "") {
        return;
      }
      this.$emit('todo-added', this.label);
    }
    
  4. 再次尝试你的表单。现在你将无法将空项目添加到待办事项列表中。

Our todo list app rendered with a text input to enter new todos

使用 v-model 更新輸入值

在我们的 ToDoForm 组件中,还有一件事需要修复,那就是提交后,<input> 仍然包含旧值。但要修复这一点很容易,因为我们使用 v-model 将数据绑定到 ToDoForm 中的 <input>,如果我们将 name 参数设置为等于空字符串,则输入也将更新。

将你的 ToDoForm 组件的 onSubmit() 方法更新为此方法

js
onSubmit() {
  if (this.label === "") {
    return;
  }
  this.$emit('todo-added', this.label);
  this.label = "";
}

现在,当你点击 "添加" 按钮时,"new-todo-input" 将会清除自身。

總結

太好了。现在我们可以向我们的表单添加待办事项了!我们的应用程序现在开始变得互动起来,但一个问题是,我们到目前为止完全忽略了它的外观和感觉。在下一篇文章中,我们将集中精力解决这个问题,看看 Vue 提供的几种样式化组件的方式。