Template Syntax Rendering
In this chapter, you will learn to use Vue's template syntax to render JS data into HTML, allowing the blog homepage to display a list of article cards.
Interpolation Expressions {{ }}
Interpolation expressions are the most basic data binding method in Vue.
They "insert" the value of a JS variable into HTML, with the syntax being double curly braces wrapping the variable name.
Example
<script setup>
const blogTitle = 'TUTORIAL Frontend Notes'
const author = 'Xiao Ming'
</script>
<template>
<h1>{{ blogTitle }}</h1>
<p>Author: {{ author }}</p>
<p>Current time: {{ new Date().toLocaleDateString() }}</p>
</template>
The page will render:
TUTORIAL Frontend NotesAuthor: Xiao MingCurrent time: 2024/5/15
You can put any JS expression inside the curly braces: variables, operations, ternary expressions, function callsβall are allowed, but statements (such as if, for) are not.
Interpolation expressions only replace the text inside tags, not the tags themselves. For example
{{ content }}the
tag itself is not affected.
Attribute Binding v-bind (Shorthand :)
Interpolation expressions can only be used for tag content, not for HTML attributes.
To use JS variables in attributes, you need the v-bind directive.
Example
<script setup>
const coverUrl = '/images/vue-cover.png'
const linkUrl = '/post/1'
const isActive = true
</script>
<template>
<!-- Full syntax: v-bind:attributeName="variable" -->
<img v-bind:src="coverUrl" v-bind:alt="Article Cover">
<!-- Shorthand: directly use :attributeName="variable" -->
<a :href="linkUrl">Read Full Article</a>
<!-- Dynamic class: using object syntax -->
<div :class="{ active: isActive, 'text-bold': true }">
This text has both active and text-bold classes
</div>
</template>
The shorthand colon is the most common way to write it; in actual development, the full v-bind: is rarely used.
List Rendering v-for
A blog homepage needs to display multiple articles. One article is an single object, and multiple articles form an array of objects.
v-for can loop through arrays to generate corresponding HTML for each element.
Example
<script setup>
// Simulating article data with a JS object array (will later be changed to load from JSON file)
const articles = [
{
id: 1,
title: 'Vue3 Complete Beginner Guide',
summary: 'Start learning Vue3 Composition API from scratch, covering core concepts like ref, reactive, computed, etc.',
date: '2024-05-10',
category: 'Vue',
cover: '/images/vue.png'
},
{
id: 2,
title: 'JavaScript Asynchronous Programming Explained',
summary: 'Understand Promise, async/await, event loop, and microtask queue in one article.',
date: '2024-05-08',
category: 'JavaScript',
cover: '/images/js.png'
},
{
id: 3,
title: 'CSS Grid Layout in Practice',
summary: 'Easily implement complex responsive layouts with CSS Grid, saying goodbye to the limitations of float and flexbox.',
date: '2024-05-05',
category: 'CSS',
cover: '/images/css.png'
}
]
</script>
<template>
<div class="article-list">
<!-- v-for="item in array" :key="unique identifier" -->
<div v-for="article in articles" :key="article.id"class="card">
<img :src="article.cover" :alt="article.title">
<div class="card-body">
<span class="category">{{ article.category }}</span>
<h3>{{ article.title }}</h3>
<p>{{ article.summary }}</p>
<span class="date">{{ article.date }}</span>
</div>
</div>
</div>
</template>
<style scoped>
.article-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
margin-top: 20px;
}
.card {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
transition: transform 0.2s;
}
.card:hover {
transform: translateY(-4px);
}
.card img {
width: 100%;
height: 180px;
object-fit: cover;
}
.card-body {
padding: 16px;
}
.category {
display: inline-block;
padding: 2px 10px;
background: #e8f5e9;
color: #42b883;
border-radius: 12px;
font-size: 12px;
}
.card-body h3 {
margin: 10px 0 8px;
font-size: 18px;
}
.card-body p {
color: #666;
font-size: 14px;
line-height: 1.6;
}
.date {
display: block;
margin-top: 10px;
font-size: 12px;
color: #999;
}
</style>
The Importance of the key Attribute
:key is an essential partner to v-for. It tells Vue the identity of each element.
When the array order changes (such as insertion, deletion, or sorting), Vue uses key to determine which DOM needs to be updated and which can be reused.
| key value | Consequence |
|---|---|
| Unique id (recommended) | Correctly tracks each element, efficient updates |
| Array index | May cause state confusion during insertion/deletion (e.g., input contents swapping) |
| No key | Console warning, Vue defaults to "in-place update" strategy, poorer performance |
Always add a unique and stable key to v-for, and never use index as the key. This is one of the most important habits in Vue development.
Conditional Rendering v-if / v-else
When you need to display different content under different conditions, use the v-if family of directives.
Example
<script setup>
import { ref } from 'vue'
const articles = ref([]) // Article list (empty array means no data yet)
const isLoading = ref(true) // Loading state
// Simulating data loading: fill data after 2 seconds
setTimeout(() => {
articles.value = [
{ id: 1, title: 'Vue3 Beginner', summary: '...' },
{ id: 2, title: 'React Comparison', summary: '...' }
]
isLoading.value = false
}, 2000)
</script>
<template>
<div>
<!-- Loading -->
<p v-if="isLoading">Loading, please wait...</p>
<!-- Show list when data exists -->
<div v-else-if="articles.length > 0">
<div v-for="article in articles" :key="article.id"class="card">
<h3>{{ article.title }}</h3>
<p>{{ article.summary }}</p>
</div>
</div>
<!-- Empty state when no data -->
<p v-else>No articles yet, stay tuned.</p>
</div>
</template>
This pattern covers the three most common UI states: loading β has data β empty data.
v-if vs v-show
| Directive | Toggle Method | Initial Render Cost | Toggle Cost | Suitable Scenarios |
|---|---|---|---|---|
| v-if | Add/remove DOM nodes | Low (not rendered) | High (rebuilds DOM) | Conditions that rarely change, such as permission control |
| v-show | Toggle CSS display | High (always rendered) | Low (only changes style) | Frequently toggled scenarios, such as tab switching |
Simple memory aid: use v-show for frequent toggling, use v-if for stable conditions.
Hands-on: Complete Blog Homepage
Combine the template syntax learned earlier to write a complete blog homepage component.
Example
<script setup>
import { ref } from 'vue'
// Article data (currently hardcoded, will be changed to fetch loading in later chapters)
const articles = ref([
{
id: 1,
title: 'Vue3 Complete Beginner Guide',
summary: 'Start learning Vue3 Composition API from scratch, covering core concepts like ref, reactive, computed, etc.',
date: '2024-05-10',
category: 'Vue',
cover: '/images/vue.png'
},
{
id: 2,
title: 'JavaScript Asynchronous Programming Explained',
summary: 'Understand Promise, async/await, event loop, and microtask queue in one article.',
date: '2024-05-08',
category: 'JavaScript',
cover: '/images/js.png'
},
{
id: 3,
title: 'CSS Grid Layout in Practice',
summary: 'Easily implement complex responsive layouts with CSS Grid.',
date: '2024-05-05',
category: 'CSS',
cover: '/images/css.png'
}
])
</script>
<template>
<div class="home">
<h2 class="section-title">Latest Articles</h2>
<!-- Loading -->
<p v-if="!articles.length"class="empty-tip">No articles yet, stay tuned.</p>
<!-- Article card list -->
<div v-else class="article-grid">
<div
v-for="article in articles"
:key="article.id"
class="article-card"
>
<img :src="article.cover" :alt="article.title"class="card-cover">
<div class="card-content">
<span class="card-category">{{ article.category }}</span>
<h3 class="card-title">{{ article.title }}</h3>
<p class="card-summary">{{ article.summary }}</p>
<span class="card-date">{{ article.date }}</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.home {
max-width: 960px;
margin: 0 auto;
padding: 20px;
}
.section-title {
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
}
.article-card {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
transition: transform 0.2s, box-shadow 0.2s;
cursor: pointer;
}
.article-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.card-cover {
width: 100%;
height: 180px;
object-fit: cover;
}
.card-content {
padding: 16px;
}
.card-category {
display: inline-block;
padding: 2px 10px;
background: #e8f5e9;
color: #42b883;
border-radius: 12px;
font-size: 12px;
margin-bottom: 8px;
}
.card-title {
font-size: 18px;
margin-bottom: 8px;
color: #222;
}
.card-summary {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-bottom: 12px;
}
.card-date {
font-size: 12px;
color: #999;
}
.empty-tip {
text-align: center;
color: #999;
padding: 60px 0;
font-size: 16px;
}
</style>
Chapter Summary
In this chapter, you learned four core template syntaxes: {{ }} interpolation for displaying data, v-bind (shorthand :) for binding attributes, v-for for looping and rendering lists, and v-if/v-else for conditional control of visibility.
With this, the blog homepage can now dynamically render article cards based on JS data.
YouTip