创建第一个 Vue 组件
现在是时候深入了解 Vue,并创建我们自己的自定义组件了--我们将从创建一个组件来表示待办事项列表中的每个项目开始。在这一过程中,我们将学习一些重要的概念,例如在其他组件中调用组件,通过道具向它们传递数据,以及保存数据状态。
备注:如果你需要根据我们的版本检查你的代码,你可以在我们的 todo-vue 仓库找到中找到示例 Vue 程序代码的完成版本。有关运行中的实时版本,请参见 https://mdn.github.io/todo-vue/。
前提: |
熟悉HTML、CSS、JavaScript核心语言,了解终端或命令行。 Vue 组件是由管理应用程序数据的 JavaScript 对象和映射到基础 DOM 结构的基于 HTML 的模板语法组成的。对于安装,以及使用 Vue 的一些更高级的功能(如单文件组件或渲染函数),你需要一个安装了 node 和 npm 的终端。 |
---|---|
目标: | 学习如何创建一个 Vue 组件,将其渲染到另一个组件中,使用props将数据传递到组件中,并保存其状态。 |
创建一个 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>
里面,让我们添加一个checkbox
和一个对应的label
。给复选框添加一个id
,并添加一个for
属性,将复选框映射到标签上,如下所示:html<template> <div> <input type="checkbox" id="todo-item" checked="false" /> <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 中的表示方式总是用大写驼色(例如 ToDoList
),而等价的自定义元素总是用连字符小写(例如 <to-do-list>
)。
- 在
<h1>
下面,创建一个无序列表 (<ul>
),其中包含一个列表项 (<li>
)。 - 在列表项 (<li>) 里面添加
<to-do-item></to-do-item>
.
你的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 必须唯一),而且我们没有办法设置 label 标签的文本。这一切都不是动态的。
我们需要的是一些组件状态。这可以通过在组件中添加 props 来实现。你可以认为 props 与函数中的输入类似。prop 的值给予了组件影响其显示的初始状态。
注册 props
在 Vue 中,注册 props 的方式有两种:
- 第一种方式是,以字符串数组的方式列出 props,数组中的每个实体对应一个 prop 名称。
- 第二种方法是将 props 定义为一个对象,每个 key 对应于 prop 名称。将 props 列为对象允许你指定默认值,将 props 标记为 required,执行基本的对象类型 (特别是 JavaScript 基本类型) ,并执行简单的 prop 校验。
备注:注意:prop 验证只能在 development 模式下进行,所以你不能在生产环境中严格依赖它。此外,prop 验证函数在组件实例创建之前被调用,因此它们不能访问组件状态 (或其他 props)。
针对 ToDoItem 组件,我们将使用对象注册法。
- 回到
ToDoItem.vue
文件。 - 在默认导出的
default {}
对象中添加一个props
属性,该 props 属性含有一个空对象。 - 在这个对象里,添加两个 key 为
label
和done
属性。 label
的值应该是一个带有两个属性的对象(或者是 props,因为它们被调用在可找到的组件的 context)- 第一个
required
属性,它的值是true
. 这将会告诉 Vue 说,我们希望每个该组件的实例都必须有个 label 字段。如果ToDoItem
组件没有 label 字段的话,Vue 会提示警告。 - 第二是添加一个
type
属性。这个属性的值设为 JavaScript 的String
类型。这等于告诉 Vue,我们希望 type 属性的值是 String 类型的。
- 第一个
- 现在转向
done
prop.- 首先添加一个
default
属性,它的值是false
。这意味着当没有done
prop 被传递给ToDoItem
组件时,done
prop 的值会是 false(注意 default 属性不是必需的————我们只在非 required props 里才需要default
) - 接着,添加一个
type
属性,值为Boolean
。这将告诉 Vue,我们希望这个 prop 的值是 JavaScript 的 Boolean 类型。
- 首先添加一个
你的组件对象现在看起来应该像是这样:
<script>
export default {
props: {
label: { required: true, type: String },
done: { default: false, type: Boolean }
}
};
</script>
使用已注册的 props
随着组件对象中这些 props 的定义,我们可以在 template 里使用这些变量值。让我们开始向组件模版中添加 label
prop。
在你的 <template>
中,将 <label>
标签的 contents 内容修改为 {{label}}
。
{{}}
是 Vue 中的一个特殊的模版语法,它能在模版内打印类中定义的 JavaScript 表达式的结果,包括值和方法。重要的是,{{}}
里的内容是作为文本显示,而非 HTML。在此例中,我们打印的是 label
的值。
现在,你组件的 template 部分应该是像这样:
<template>
<div>
<input type="checkbox" id="todo-item" checked="false" />
<label for="todo-item">{{label}}</label>
</div>
</template>
回到网页浏览器中,你将会看到 todo item 与之前相同,但是没有 label。转到浏览器的开发者工具,你会在控制台中看到这样的警告:
[Vue warn]: Missing required prop: "label" found in ---> <ToDoItem> at src/components/ToDoItem.vue <App> at src/App.vue <Root>
这是因为我们将 label
prop 标记为 required, 但我们从未给组件这个 prop————我们已经在 template 内定义了希望使用这个 prop 值的位置,但在调用时我们没有把它传递进组件。让我们修复这个问题。
在我们的 App.vue
文件中,像常规 HTML 属性那样,在 <to-do-item></to-do-item>
里添加一个 label
属性:
<to-do-item label="My ToDo Item"></to-do-item>
现在你会在页面上看到 label 了,并且 console 里不会再出现警告提示。
这就是一个简单的 props。接下来我们将讨论 Vue 如何持久化数据状态
Vue 的数据对象
如果你改变 传递到在你的 App 组件中访问的 <to-do-item></to-do-item>
的 label
prop 值,你应该会看到该值被更新。这很 ok。我们现在有了一个 checkbox,一个可更新的 label 标签。然而,我们目前没有对 "done" prop 做任何事情——我们可以在 UI 中选中 checkbox,但在应用程序中,我们没有实际记录 todo item 是否已完成。
为了实现它,我们将组件的 done
prop 与 <input>
标签的 checked
属性作绑定。这样就可以记录复选框是否被选中。但是,props 是一种单向数据绑定,组件永远不该改变自己的 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"
换句话说,你可以在要绑定到的任何 attribute/prop 前面加上 v-bind:
。在大多数情况下,你可以对 v-bind:
属性使用速记,即只在 attribute/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="false"
替换为 :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
。
我们可以使用 lodash 包的 uniqueid()
方法来帮助保持索引的唯一性。此包导出一个函数,该函数接收字符串并将唯一整数追加到前缀的末尾。这足以保持组件 id
的唯一性。
让我们使用 npm 将包添加到我们的项目中;停止服务器并在终端中输入以下命令:
npm install --save lodash.uniqueid
备注:如果你更喜欢 yarn,则可以改用 yarn add lodash.uniqueid
。
现在,我们可以将此包导入我们的 ToDoItem
组件中。在 ToDoItem.vue
的 <script>
元素的顶部添加以下行:
import uniqueId from "lodash.uniqueid";
接下来,将字段 id
添加到我们的数据属性中,这样组件对象最终看起来就是这样(uniqueId()
返回指定的前缀——todo
——并附加一个唯一的字符串):
import uniqueId from "lodash.uniqueid";
export default {
props: {
label: { required: true, type: String },
done: { default: false, type: Boolean },
},
data() {
return {
isDone: this.done,
id: uniqueId("todo-"),
};
},
};
接下来,将 id
绑定到复选框的 id
属性和标签的 for
属性,更新现有 id
和 for
属性,如下所示:
<template>
<div>
<input type="checkbox" :id="id" :checked="isDone" />
<label :for="id">{{label}}</label>
</div>
</template>