Components

Components are the primary way of reusing code in a Vue application. With components, you can extend HTML elements and provide additional logic to make them reusable. You can define your elements and use them in the same way as native HTML elements. As we mentioned earlier, a Vue component is also a Vue instance. It accepts the same options object during creation.

You can register a component globally so that it is available across the entire application. To do that, you can use Vue.component(id, [definition]). The first argument is component id. It is also the tag name that you will use in the template. And the second argument is definition of the component, which can be an options object or a function that will return an options object. And you can also register a component locally to make it only available in the scope of another Vue instance. To do that, you add the component's options object to its parent's components property.

Let's update our Messages App to use a component to render the messages list. We call this component MessageListand its tag name is message-list. It will extend the <ul> HTML element in our template, as well as everything inside of it. This component will also need to react to clicking on the Delete button.

In order to render the list, the MessageList component will need to access the messages property of the data object. In Vue.js, components have their own isolated scopes, meaning you cannot directly reference parent data in a child component. Vue.js provides a property called props in the options object. You can use it to define the data that can be passed to a component. The props property takes an array or an object as its value. Let's add property items to the MessageList component and use the v-bind directive to connect it to the messages data.

Now, we need to think about message delete actions. Since all of the messages are stored in the messages property, and we pass them to MessageList via the items property, it seems quite straightforward that we perform the delete operation directly within MessageList. This encapsulate logic inside the component itself. Is this a good approach? Let's step back and think about it. We put the rendering logic and the deleting logic into the MessageList component, but we have left the adding logic outside since <form> is not part of the MessageList component. Right, in that case, we can put that into MessageList too. Wait, does that sound like something MessageList should do? A component should only focus on one responsibility. We should make MessageList focus only on rendering the messages list. In that way, we can use it in other places that only require rendering but not adding or deleting. So, we will not move <form> into MessageList, nor will we delete the messages inside it.

Basically, we need a way to communicate back to the parent when a Delete button is clicked on a message inside the MessageList component. In Vue.js, we can do that with custom events. A Vue instance has a $emit() method that can trigger an event on the current instance. It takes the event name as the first argument and any additional data, if there is any, as the second argument. And the parent can use the v-on directive to attach listeners to that event. So, we can let MessageList trigger a delete event upon clicking on the Delete button and pass through the message that should be deleted.

Now, let's update the index.html file to use this component, even though we haven't defined it yet:

<div id="app" v-cloak>
<message-list :items="messages" @delete="deleteMessage"></message-
list>
<ul>
<li v-for="message in messages">
{{ message.text }} - {{ message.createdAt }}
<button @click="deleteMessage(message)">X</button>
</li>
</ul>
...
</div>

:items="messages" is the shorthand of v-bind:items="messages", and @delete="deleteMessage" is the shorthand of v-on:delete="deleteMessage". As you can see, we can do data binding and attach event listeners to the same with custom components and native HTML elements. It's another level of simplicity.

It's time to create the MessageList component. We can stay in index.html and put the code of MessageList above let vm = new Vue({...})because Vue.js needs to register components first to use them in the parent. Another way, which is recommended, is to put everything that a component requires in a separate file so that we can use import to reuse it wherever needs it. Let's create a file called MessageList.js under the components folder.

Let's have a look at the components/MessageList.js file:

1.  export default {
2. name: 'MessageList',
3. template: `<ul>
4. <li v-for="item in items" :item="item">
5. {{ item.text }} - {{ item.createdAt }}
6. <button @click="deleteMessage(item)">X</button></li></ul>`,
7. props: {
8. items: {
9. type: Array,
10. required: true
11. }
12. },
13. methods: {
14. deleteMessage (message) {
15. this.$emit('delete', message);
16. }
17. }
18. };

This file is an ES6 module. It exports the component's definition, the options object, as the default export. In line 2, we add the name property, which is not mandatory, but it will help you during debugging and it is good practice to add it.

In line 3, we copy the <ul> object and everything inside of it from index.html here as an inline template. And we also add the deleteMessage() method in line 14 to listen to the click event on the Delete button. Inside this method, we use $emit() to trigger the delete event of MessageList so that its parent can listen to it via @delete="...". You don't have to name the event delete; you can call it onDelete and trigger it like this.$emit('onDelete', ...) and use @onDelete="..." to listen to it.

In lines 7 to 12, we define the items property of MessageList. Here, we use an object instead of an array as its definition to give Vue.js details of the structure of the data that it needs to have. Here, we define it as an array and mandatory. If we mistakenly passed in a string or other value that is not an array, Vue.js will throw an error, so that we can capture this issue during development rather than troubleshoot it during production.

Now, let's register MessageList to the root Vue instance in index.html:

<script type="module">
import MessageList from './components/MessageList.js';
let vm = new Vue({
...
components: {
MessageList
},
...
});
</script>

Since we create MessageList.js as an ES6 module, we need to add type="module" to the <script> tag and import MessageList. To register MessageList as a local component, we add it to the options object's components property by using the shorthand of MessageList:MessageList. Vue.js will convert the property name from Pascal case to kebab-case and use it as the component ID when registering it so that we can use it as <message-list> in the template.

Our Messages App is very simple and only has a few lines of code. An actual messaging application in the real world, for example, Disqus, supports features such as replies, markdown syntax, and embedding images. And if the Messages App was such an application, we do not want to only have a MessageList component. We want to have dedicated components to encapsulate specific features. A good start would be to move a single message rendering to a separate component, for example, MessageListItem. Let's do that.

Before we do that, let's talk about naming. MessageList is a long name and MessageListItem is even longer. So, why not just use Messages or Msgs for the list component and Message or Msg for the single message component? Most of the time, when you give something a name in the code, such as variable names and filenames, you need to consider whether or not that name is a good reflection of the domain that you're working on, and then whether or not it follows the naming convention or style guide. And then, you can turn to picking a shorter one. Vue.js has an official style guide. There is one guide about tightly coupled component names, which states: Child components that are tightly coupled with their parent should include the parent component name as a prefix. So, we're following that. And when you see the name MessageListItem, you know that its parent component is MessageList.

The MessageListItem component will extend the <li> tag and everything inside of it. And here is how it appears. Let's have a look at the components/MessageListItem.js file:

export default {
name: 'MessageListItem',
template: `<li>{{ item.text }} - {{ item.createdAt }}
<button @click="deleteClicked">X</button></li>`,
props: {
item: {
type: Object,
required: true
}
},
methods: {
deleteClicked () {
this.$emit('delete');
}
}
};

We define this component to accept data from the parent component using the property item, and it will trigger a delete event when clicking on the Delete button.

Here is the change to the MessageList component:

import MessageListItem from './MessageListItem.js';
export default {
name: 'MessageList',
template: `<ul><message-list-item v-for="item in items" :item="item"
@delete="deleteMessage(item)"></message-list-item></ul>`,
...
components: {
MessageListItem
}
...
};

As you can see, we import MessageListItem from the same directory and register it locally to MessageList and replace <li> with <message-list-item>. The v-for directive works the same way, and we pass data using :item and bind the listener using @delete.

When you run the application, you will see a tip from Vue.js in the console that says that component lists rendered with v-for should have explicit keys. In short, Vue.js is asking us to provide a key, which can be the unique id of the items in the list, so that it can track which element has been changed and only update that part of the DOM. It's related to the virtual DOM, which Vue.js 2 uses to keep track of changes it needs to make to the real DOM. We will talk about the virtual DOM in detail in a later section. For now, let's bind key for <message-list-item>.

Let's add the id property to message objects using the timestamp of creation, which is good enough in our case. Here are the changes to the addMessage() method:

addMessage (event) {          
...
let now = new Date();
this.messages.push({
id: now.getTime(), text:this.newMessage, createdAt:now});
...
}

And here is the change to the MessageList component:

export default {
...
template: `<ul><message-list-item v-for="item in items"
:item="item" :key="item.id" @delete="deleteMessage(item)">
</message-list-item></ul>`,
...
};

By now, we have created a component-based Vue application. Here is its final structure:

/index.html
/components/MessageList.js
/components/MessageListItem.js

For building large-scale web applications, it is ideal to use single file components. With that, basically, you can put the template, the JavaScript code, and the CSS of a component in a single file that uses the .vue extension. We will see how that works when we build the TaskAgile app later.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.150.231