Vue.js Fundamentals
The Progressive JavaScript Framework
Explanation
Why Vue?
Vue.js is called the "progressive framework" because you can adopt it incrementally. Start with a simple script tag and scale up to a full SPA. It combines the best ideas from React and Angular.
Think of Vue like a well-designed tool:
- Easy to pick up
- Powerful when you need it
- Gets out of your way
Key Concepts
- Reactivity: Data changes automatically update the DOM
- Components: Reusable, self-contained pieces
- Directives: Special attributes (v-if, v-for, v-bind)
- Composition API: Modern way to organize logic (Vue 3)
Vue vs React
| Feature | Vue | React | |---------|-----|-------| | Template | HTML-based | JSX | | Reactivity | Automatic | Manual (useState) | | Learning curve | Gentle | Moderate | | Two-way binding | Built-in (v-model) | Manual | | Official router/state | Yes | Community |
Demonstration
Example 1: Vue Basics (Options API)
<template>
<div class="counter">
<h1>Count: {{ count }}</h1>
<p>Double: {{ doubleCount }}</p>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
<button @click="increment">+</button>
</div>
</template>
<script>
export default {
name: 'Counter',
// Reactive data
data() {
return {
count: 0
}
},
// Computed properties (cached)
computed: {
doubleCount() {
return this.count * 2
}
},
// Methods
methods: {
increment() {
this.count++
},
decrement() {
this.count--
},
reset() {
this.count = 0
}
},
// Lifecycle hooks
mounted() {
console.log('Component mounted!')
}
}
</script>
<style scoped>
.counter {
text-align: center;
padding: 20px;
}
button {
margin: 0 5px;
padding: 10px 20px;
}
</style>
Example 2: Composition API (Vue 3)
<template>
<div class="user-profile">
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<button @click="refresh">Refresh</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// Reactive state
const user = ref(null)
const loading = ref(true)
const error = ref(null)
// Fetch function
const fetchUser = async () => {
loading.value = true
error.value = null
try {
const response = await fetch('/api/users/1')
user.value = await response.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
// Refresh handler
const refresh = () => {
fetchUser()
}
// Lifecycle
onMounted(() => {
fetchUser()
})
</script>
Example 3: Forms and v-model
<template>
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label>Name</label>
<input v-model="form.name" required>
</div>
<div class="form-group">
<label>Email</label>
<input v-model="form.email" type="email" required>
</div>
<div class="form-group">
<label>Role</label>
<select v-model="form.role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" v-model="form.newsletter">
Subscribe to newsletter
</label>
</div>
<button type="submit" :disabled="!isValid">
{{ submitting ? 'Saving...' : 'Save' }}
</button>
</form>
<pre>{{ form }}</pre>
</template>
<script setup>
import { reactive, computed, ref } from 'vue'
const form = reactive({
name: '',
email: '',
role: 'user',
newsletter: false
})
const submitting = ref(false)
const isValid = computed(() => {
return form.name.length > 0 && form.email.includes('@')
})
const handleSubmit = async () => {
submitting.value = true
try {
await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(form)
})
alert('Saved!')
} finally {
submitting.value = false
}
}
</script>
Key Takeaways:
ref()for primitives,reactive()for objectsv-modelfor two-way binding@clickis shorthand forv-on:click:disabledis shorthand forv-bind:disabled<script setup>is the modern, concise syntax
Imitation
Challenge 1: Build a Todo List
Task: Create a todo list with add, complete, and delete functionality.
Solution
<script setup>
import { ref } from 'vue'
const newTodo = ref('')
const todos = ref([])
const addTodo = () => {
if (newTodo.value.trim()) {
todos.value.push({
id: Date.now(),
text: newTodo.value,
completed: false
})
newTodo.value = ''
}
}
const toggleTodo = (id) => {
const todo = todos.value.find(t => t.id === id)
if (todo) todo.completed = !todo.completed
}
const deleteTodo = (id) => {
todos.value = todos.value.filter(t => t.id !== id)
}
</script>
<template>
<input v-model="newTodo" @keyup.enter="addTodo">
<ul>
<li v-for="todo in todos" :key="todo.id">
<span :class="{ done: todo.completed }" @click="toggleTodo(todo.id)">
{{ todo.text }}
</span>
<button @click="deleteTodo(todo.id)">X</button>
</li>
</ul>
</template>
Challenge 2: Create a Search Filter
Task: Filter a list of items as the user types.
Solution
<script setup>
import { ref, computed } from 'vue'
const search = ref('')
const items = ref(['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'])
const filteredItems = computed(() => {
return items.value.filter(item =>
item.toLowerCase().includes(search.value.toLowerCase())
)
})
</script>
<template>
<input v-model="search" placeholder="Search...">
<ul>
<li v-for="item in filteredItems" :key="item">{{ item }}</li>
</ul>
</template>
Practice
Exercise 1: User Card Component
Difficulty: Beginner
Create a reusable UserCard component that accepts props:
- name, email, avatar, role
- Emit an event when clicked
Exercise 2: Data Fetching Composable
Difficulty: Intermediate
Create a reusable composable for data fetching:
const { data, loading, error, refetch } = useFetch('/api/users')
Summary
What you learned:
- Vue component structure (template, script, style)
- Reactivity with ref() and reactive()
- Directives: v-if, v-for, v-model, v-bind, v-on
- Composition API for organizing logic
Next Steps:
- Read: Angular Fundamentals
- Practice: Build a weather app with Vue
- Build: Create a full SPA with Vue Router
