Components — Splitting HTML
The Template Is Getting Big
The Bookshelf app from the previous chapter has grown to a reasonable size.
The template includes cards, a search bar, and tag filters.
As applications grow, writing everything in a single template becomes painful.
Even with plain HTML, you'd want to split by section.
In Vue, you can extract parts of a template into components.
What Is a Component?
A component is like defining your own HTML tag.
HTML has built-in elements like <div>, <span>, <input>.
With components, you can create your own elements like <book-card> or <search-bar>.
<!-- This is the goal -->
<div id="app">
<h1>My Bookshelf</h1>
<search-bar></search-bar>
<tag-filter></tag-filter>
<book-card v-for="book in filteredBooks" :book="book"></book-card>
</div>
Similar to HTML Custom Elements (Web Components).
Just write <book-card> in the template, and its content expands.
Defining a Component
In a CDN environment, register components with app.component().
Inside setup(), receive props and return values for the template.
app.component('book-card', {
props: ['book'],
setup(props) {
return { book: props.book }
},
template: `
<div class="card">
<h2>{{ book.title }}</h2>
<p>{{ book.description }}</p>
<span class="tag" v-for="tag in book.tags">
{{ tag }}
</span>
</div>
`
})
Notice the template content.
It's exactly the card HTML from the previous chapter, moved as-is.
props: Receiving Data from Parent
props: ['book'] declares that this component receives book data from outside.
The props received in setup(props) are reactive — when parent data changes, the component updates automatically.
<book-card :book="book"></book-card>
:book="book" — that's v-bind. Passing parent data to the child.
Written as an HTML attribute, as always.
emit: Child-to-Parent Communication
Data flow between components is one-directional.
- Parent → Child:
props(:book="book") - Child → Parent:
emit(@select="toggleTag")
Look at the search-bar component's setup().
setup(props, { emit }) {
const onInput = (e) => emit('update:query', e.target.value)
return { query: props.query, onInput }
}
emit is received from setup()'s second argument.
On each input, it fires an update:query event, and the parent catches it to update searchQuery.
Same concept as HTML's event model (addEventListener / dispatchEvent).
Looking at the Root Template
With components, the root template becomes:
<h1>My Bookshelf</h1>
<search-bar :query="searchQuery" @update:query="searchQuery = $event"></search-bar>
<tag-filter :tags="allTags" :selected="selectedTag" @select="toggleTag"></tag-filter>
<book-card v-for="book in filteredBooks" :book="book"></book-card>
It reads like HTML custom elements.
The app structure is clear at a glance: search bar, tag filter, book cards.
Each component's template is also HTML.
Looking inside a component, you can immediately see what it renders.
Summary
- Components are like HTML custom elements
propsreceive data from parent — passed as HTML attributessetup(props, { emit })receives props and emitemitsends events from child to parent — same as HTML's event model- Even split into components, each part remains HTML
- App structure is readable from the template
- Still just a single
index.htmlfile (but in the next chapter...)