Now it's time to dive deeper into Vue, and create our own custom component — we'll start by creating a component to represent each item in the todo list. Along the way, we'll learn about a few important concepts such as calling components inside other components, passing data to them via props, and saving data state.
注意: If you need to check your code against our version, you can find a finished version of the sample Vue app code in our todo-vue repository . For a running live version, see https://mdn.github.io/todo-vue/dist/ .
| Prerequisites: |
Familiarity with the core
HTML
,
CSS
,和
JavaScript
languages,
knowledge of the
terminal/command line
.
Vue components are written as a combination of JavaScript objects that manage the app's data and an HTML-based template syntax that maps to the underlying DOM structure. For installation, and to use some of the more advanced features of Vue (like Single File Components or render functions), you'll need a terminal with 节点 and npm installed. |
|---|---|
| Objective: | To learn how to create a Vue component, render it inside another component, pass data into it using props, and save its state. |
Let's create our first component, which will display a single todo item. We'll use this to build our list of todos.
moz-todo-vue/src/components
directory, create a new file named
ToDoItem.vue
. Open the file in your code editor.
<template></template>
to the top of the file.
<script></script>
section below your template section. Inside the
<script>
tags, add a default exported object
export default {}
, which is your component object.
Your file should now look like this:
<template> </template>
<script>
export default {};
</script>
We can now begin to add actual content to our
ToDoItem
. Vue templates are currently only allowed a single root element — one element needs to wrap everything inside the template section (this will change when Vue 3 comes out). We'll use a
<div>
for that root element.
<div>
inside your component template now.
<div>
, let's add a checkbox and a corresponding label. Add an
id
to the checkbox, and a
for
attribute mapping the checkbox to the label, as shown below.
<template>
<div>
<input type="checkbox" id="todo-item" />
<label for="todo-item">My Todo Item</label>
</div>
</template>
This is all fine, but we haven’t added the component to our app yet, so there’s no way to test it and see if everything is working. Let’s add it now.
App.vue
再次。
<script>
tag, add the following to import your
ToDoItem
component:
import ToDoItem from './components/ToDoItem.vue';
components
property, and inside it add your
ToDoItem
component to register it.
您的
<script>
contents should now look like this:
import ToDoItem from './components/ToDoItem.vue';
export default {
name: 'app',
components: {
ToDoItem
}
};
This is the same way that the
HelloWorld
component was registered by the Vue CLI earlier.
To actually render the
ToDoItem
component in the app, you need to go up into your
<template>
element and call it as a
<to-do-item></to-do-item>
element. Note that the component file name and its representation in JavaScript is always in PascalCase (e.g.
ToDoList
), and the equivalent custom element is always in kebab-case (e.g.
<to-do-list>
).
<h1>
, create an unordered list (
<ul>
) containing a single list item (
<li>
).
<to-do-item></to-do-item>
.
您的
App.vue
<template>
contents should now look something like this:
<div id="app">
<h1>To-Do List</h1>
<ul>
<li>
<to-do-item></to-do-item>
</li>
</ul>
</div>
If you check your rendered app again, you should now see your rendered
ToDoItem
, consisting of a checkbox and a label.
我们的
ToDoItem
component is still not very useful because we can only really include this once on a page (IDs need to be unique), and we have no way to set the label text. Nothing about this is dynamic.
What we need is some component state. This can be achieved by adding props to our component. You can think of props as being similar to inputs in a function. The value of a prop gives components an initial state that affects their display.
In Vue, there are two ways to register props:
注意: Prop validation only happens in development mode, so you can't strictly rely on it in production. Additionally, prop validation functions are invoked before the component instance is created, so they do not have access to the component state (or other props).
For this component, we’ll use the object registration method.
ToDoItem.vue
文件。
props
property inside the export
default {}
object, which contains an empty object.
label
and
done
.
label
key's value should be an object with 2 properties (or
props
, as they are called in the context of being available to the components).
required
property, which will have a value of
true
. This will tell Vue that we expect every instance of this component to have a label field. Vue will warn us if a
ToDoItem
component does not have a label field.
type
property. Set the value for this property as the JavaScript
字符串
type (note the capital "S"). This tells Vue that we expect the value of this property to be a string.
done
prop.
default
field, with a value of
false
. This means that when no
done
prop is passed to a
ToDoItem
component, the
done
prop will have a value of false (bear in mind that this is not required — we only need
default
on non-required props).
type
field with a value of
布尔
. This tells Vue we expect the value prop to be a JavaScript boolean type.
Your component object should now look like this:
<script>
export default {
props: {
label: { required: true, type: String },
done: { default: false, type: Boolean }
}
};
</script>
With these props defined inside the component object, we can now use these variable values inside our template. Let's start by adding the
label
prop to the component template.
在
<template>
, replace the contents of the
<label>
element with
{{label}}
.
{{}}
is a special template syntax in Vue, which lets us print the result of JavaScript expressions defined in our class, inside our template, including values and methods. It’s important to know that content inside
{{}}
is displayed as text and not HTML. In this case, we’re printing the value of the
label
prop.
Your component’s template section should now look like this:
<template>
<div>
<input type="checkbox" id="todo-item" />
<label for="todo-item">{{label}}</label>
</div>
</template>
Go back to your browser and you'll see the todo item rendered as before, but without a label (oh no!). Go to your browser's DevTools and you’ll see a warning along these lines in the console:
[Vue warn]: Missing required prop: "label"
found in
---> <ToDoItem> at src/components/ToDoItem.vue
<App> at src/App.vue
<Root>
This is because we marked the
label
as a required prop, but we never gave the component that prop — we've defined where inside the template we want it used, but we haven't passed it into the component when calling it. Let’s fix that.
在
App.vue
file, add a
label
prop to the
<to-do-item></to-do-item>
component, just like a regular HTML attribute:
<to-do-item label="My ToDo Item"></to-do-item>
Now you'll see the label in your app, and the warning won't be spat out in the console again.
So that's props in a nutshell. Next we'll move on to how Vue persists data state.
If you change the value of the
label
prop passed into the
<to-do-item></to-do-item>
call in your App component, you should see it update. This is great. We have a checkbox, with an updatable label. However, we're currently not doing anything with the "done" prop — we can check the checkboxes in the UI, but nowhere in the app are we recording whether a todo item is actually done.
To achieve this, we want to bind the component's
done
prop to the
checked
属性在
<input>
element, so that it can serve as a record of whether the checkbox is checked or not. However, it's important that props serve as one-way data binding — a component should never alter the value of its own props. There are a lot of reasons for this. In part, components editing props can make debugging a challenge. If a value is passed to multiple children, it could be hard to track where the changes to that value were coming from. In addition, changing props can cause components to re-render. So mutating props in a component would trigger the component to rerender, which may in-turn trigger the mutation again.
To work around this, we can manage the
done
state using Vue’s
data
property. The
data
property is where you can manage local state in a component, it lives inside the component object alongside the
props
property and has the following structure:
data() {
return {
key: value
}
}
You'll note that the
data
property is a function. This is to keep the data values unique for each instance of a component at runtime — the function is invoked separately for each component instance. If you declared data as just an object, all instances of that component would share the same values. This is a side-effect of the way Vue registers components and something you do not want.
You use
this
to access a component's props and other properties from inside data, as you may expect. We'll see an example of this shortly.
注意:
Because of the way that
this
works in arrow functions (binding to the parent’s context), you wouldn’t be able to access any of the necessary attributes from inside
data
if you used an arrow function. So don’t use an arrow function for the
data
特性。
So let's add a
data
property to our
ToDoItem
component. This will return an object containing a single property that we'll call
isDone
, whose value is
this.done
.
Update the component object like so:
export default {
props: {
label: { required: true, type: String },
done: { default: false, type: Boolean }
},
data() {
return {
isDone: this.done
};
}
};
Vue does a little magic here — it binds all of your props directly to the component instance, so we don’t have to call
this.props.done
. It also binds other attributes (
data
, which you’ve already seen, and others like
方法
,
computed
, etc.) directly to the instance. This is, in part, to make them available to your template. The down-side to this is that you need to keep the keys unique across these attributes. This is why we called our
data
属性
isDone
而不是
done
.
So now we need to attach the
isDone
property to our component. In a similar fashion to how Vue uses
{{}}
expressions to display JavaScript expressions inside templates, Vue has a special syntax to bind JavaScript expressions to HTML elements and components:
v-bind
。
v-bind
expression looks like this:
v-bind:attribute="expression"
In other words, you prefix whatever attribute/prop you want to bind to with
v-bind:
. In most cases, you can use a shorthand for the
v-bind
property, which is to just prefix the attribute/prop with a colon. So
:attribute="expression"
works the same as
v-bind:attribute="expression"
.
So in the case of the checkbox in our
ToDoItem
component, we can use
v-bind
to map the
isDone
property to the
checked
属性在
<input>
element. Both of the following are equivalent:
<input type="checkbox" id="todo-item" v-bind:checked="isDone" />
<input type="checkbox" id="todo-item" :checked="isDone" />
You're free to use whichever pattern you would like. It's best to keep it consistent though. Because the shorthand syntax is more commonly used, this tutorial will stick to that pattern.
So let's do this. Update your
<input>
element now to include
:checked="isDone"
.
Test out your component by passing
:done="true"
到
ToDoItem
call in
App.vue
. Note that you need to use the
v-bind
syntax, because otherwise
true
is passed as a string. The displayed checkbox should be checked.
<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>
Try changing
true
to
false
and back again, reloading your app in between to see how the state changes.
Great! We now have a working checkbox where we can set the state programmatically. However, we can currently only add one
ToDoList
component to the page because the
id
is hardcoded. This would result in errors with assistive technology since the
id
is needed to correctly map labels to their checkboxes. To fix this, we can programmatically set the
id
in the component data.
We can use the
lodash
package's
uniqueid()
method to help keep the index unique. This package exports a function that takes in a string and appends a unique integer to the end of the prefix. This will be sufficient for keeping component
id
s unique.
Let’s add the package to our project with npm; stop your server and enter the following command into your terminal:
npm install --save lodash.uniqueid
注意:
If you prefer yarn, you could instead use
yarn add lodash.uniqueid
.
We can now import this package into our
ToDoItem
component. Add the following line at the top of
ToDoItem.vue
’s
<script>
元素:
import uniqueId from 'lodash.uniqueid';
Next, add an
id
field to our data property, so the component object ends up looking like so (
uniqueId()
returns the specified prefix —
todo-
— with a unique string appended to it):
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-')
};
}
};
Next, bind the
id
to both our checkbox’s
id
attribute and the label’s
for
attribute, updating the existing
id
and
for
attributes as shown:
<template>
<div>
<input type="checkbox" :id="id" :checked="isDone" />
<label :for="id">{{label}}</label>
</div>
</template>
And that will do for this article. At this point we have a nicely-working
ToDoItem
component that can be passed a label to display, will store its checked state, and will be rendered with a unique
id
each time it is called. You can check if the unique
id
s are working by temporarily adding more
<to-do-item></to-do-item>
calls into
App.vue
, and then checking their rendered output with your browser's DevTools.
Now we're ready to add multiple
ToDoItem
components to our App. In our next article we'll look at adding a set of todo item data to our
App.vue
component, which we'll then loop through and display inside
ToDoItem
components using the
v-for
指令。
最后修改: , 由 MDN 贡献者