创建我们的第一个 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 我们期望此属性的值为字符串。
- 第一个是
- 现在转到
done
prop。- 首先添加一个
default
字段,其值为false
。这意味着当没有done
prop 传递给ToDoItem
组件时,done
prop 的值将为 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>