17 KiB
Executable File
Vue.js Introduction for Python Developers
A practical guide to Vue.js, written for developers who know Python.
Table of Contents
- What is Vue?
- The Big Idea: Reactivity
- Your First Vue Component
- Reactive State with
ref - Derived State with
computed - Templates: HTML with Vue Features
- Two-Way Binding with
v-model - Events and User Interaction
- Components and Props
- Lifecycle Hooks
- Composables: Reusable Logic
- A Real-World Example: Filter Flow
- Vue Strengths, Weaknesses, and Use Cases
- Quick Reference
What is Vue?
Vue.js is a JavaScript framework for building user interfaces. It focuses on the view layer - what the user sees and interacts with.
Vue 2 vs Vue 3
This guide covers Vue 3, which introduced the Composition API (using <script setup>). If you see ref() and computed(), you're looking at Vue 3.
Python Analogy
Think of Vue components as Python classes that combine:
| Python | Vue |
|---|---|
| Class with state | <script setup> with ref() values |
| Class methods | Functions defined in <script setup> |
__str__ or template engine |
<template> section |
| Class styling | <style> section |
The Big Idea: Reactivity
Reactive means the UI automatically updates when your data changes.
# Python: you manually update the view
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
self.update_view() # you must call this manually
<!-- Vue: UI updates automatically -->
<script setup>
import { ref } from 'vue';
const count = ref(0);
function increment() {
count.value += 1; // UI updates automatically
}
</script>
<template>
<button @click="increment">Clicked {{ count }} times</button>
</template>
When count.value changes, Vue re-renders the button text. You never call update_view().
Your First Vue Component
A Vue single-file component (.vue file) has three sections:
<template>
<!-- 1. TEMPLATE: what to display -->
<p>{{ greeting }}</p>
<button @click="sayHello">Click me</button>
</template>
<script setup lang="ts">
// 2. SCRIPT: data and logic
import { ref } from 'vue';
const greeting = ref('Hello from Vue!');
function sayHello() {
console.log('Button clicked!');
}
</script>
<style scoped>
/* 3. STYLE: how it looks */
p { color: blue; }
</style>
Mental Model
When reading Vue code, ask four questions:
- What values are reactive state? → Look for
ref()andcomputed() - Which template elements use that state? → Look for
{{ variable }}andv-model - Which functions change that state? → Look for functions that modify
.value - Which computed values depend on that state? → Look for
computed()
Reactive State with ref
What is ref?
ref() wraps a value so Vue can track changes to it.
import { ref } from 'vue';
const count = ref(0); // reactive number
const username = ref('alice'); // reactive string
const isLoading = ref(false); // reactive boolean
const items = ref<string[]>([]); // reactive array
Reading and Writing
In <script>, use .value:
count.value += 1; // increment
username.value = 'bob'; // change string
isLoading.value = true; // set to true
items.value.push('apple'); // modify array
In <template>, Vue unwraps refs automatically—use the variable name without .value:
<template>
<p>{{ count }}</p> <!-- reads count.value -->
<p>{{ username }}</p> <!-- reads username.value -->
<button :disabled="isLoading">Save</button>
</template>
Python Comparison
# Python
count = 0
count += 1
// Vue (script)
const count = ref(0);
count.value += 1;
The .value exists because count is a wrapper object, not the raw value.
Example: Loading State
<template>
<button :disabled="isLoading" @click="save">
{{ isLoading ? 'Saving...' : 'Save' }}
</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const isLoading = ref(false);
async function save() {
isLoading.value = true;
try {
await fakeRequest();
} finally {
isLoading.value = false;
}
}
function fakeRequest() {
return new Promise((resolve) => setTimeout(resolve, 1000));
}
</script>
The button label and disabled state both react to isLoading.
Derived State with computed
Use computed() for values that are calculated from other reactive values.
import { ref, computed } from 'vue';
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
Python Comparison
This is like a Python @property:
class Person:
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
Rule of Thumb
Use ref when |
Use computed when |
|---|---|
| The value changes over time | The value is calculated from other values |
| The value is primary state | You don't want to duplicate state |
| Examples: user input, API data | Examples: full name from first + last |
Common Mistake: Storing What Can Be Derived
// Bad: duplicate state, must update manually
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = ref('Ada Lovelace'); // must keep in sync!
// Good: computed handles it automatically
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
Real Example: Validation
const selectedStart = ref<string | null>(null);
const selectedEnd = ref<string | null>(null);
const hasValidRange = computed(() => {
if (!selectedStart.value || !selectedEnd.value) {
return true; // missing values are valid (no filter)
}
return new Date(selectedStart.value) <= new Date(selectedEnd.value);
});
Templates: HTML with Vue Features
Interpolation: {{ }}
Print values into HTML:
<p>{{ username }}</p>
<p>{{ 2 + 3 }}</p>
<p>{{ isLoading ? 'Loading...' : 'Done' }}</p>
<p>{{ items.length }} items</p>
Conditional Rendering: v-if / v-else
Show elements based on conditions:
<p v-if="error">{{ error }}</p>
<p v-else>No errors</p>
<div v-if="isLoggedIn">
<p>Welcome, {{ username }}!</p>
</div>
<div v-else>
<p>Please log in.</p>
</div>
Loops: v-for
Repeat elements for each item:
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
Important: Always use :key with v-for for proper list rendering.
Attribute Binding: :
Bind HTML attributes to JavaScript expressions:
<button :disabled="isLoading">Save</button>
<img :src="imageUrl" />
<a :href="profileUrl">View Profile</a>
Short for v-bind:disabled.
Class Binding
<div :class="{ active: isSelected, highlight: hasError }">
Content
</div>
Two-Way Binding with v-model
v-model connects a form field and a reactive variable bidirectionally.
Basic Input
<template>
<input v-model="email" />
<p>You typed: {{ email }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const email = ref('');
</script>
- User types →
emailupdates - Code changes
email→ input display updates
Select Dropdown
<v-select
v-model="selectedCountry"
:items="countries"
item-title="title"
item-value="value"
/>
const countries = [
{ title: 'Germany', value: 'DE' },
{ title: 'France', value: 'FR' },
{ title: 'Spain', value: 'ES' },
];
const selectedCountry = ref<string | null>(null);
item-title: what's shown to usersitem-value: what's stored in the variable
Events and User Interaction
Click Events: @click
<button @click="save">Save</button>
function save() {
console.log('saving...');
}
Other Common Events
<input @input="onInput" /> <!-- typing -->
<form @submit.prevent="submitForm" /> <!-- form submission -->
<select @change="onChange" /> <!-- dropdown change -->
<div @mouseover="hovering = true" /> <!-- mouse events -->
The .prevent modifier calls event.preventDefault() automatically.
Event with Parameters
<button @click="deleteItem(id)">Delete</button>
function deleteItem(id: number) {
console.log('deleting', id);
}
Components and Props
What is a Component?
A component is a reusable UI unit. Think of it like a Python function that returns HTML, but with its own state and logic.
Parent Using Child
<!-- ParentComponent.vue -->
<template>
<UserCard :name="username" :age="34" />
</template>
<script setup lang="ts">
import UserCard from './UserCard.vue';
const username = 'Alice';
</script>
Props: Data Passed In
Props are like function arguments for components.
<!-- UserCard.vue -->
<script setup lang="ts">
interface Props {
name: string;
age: number;
mode?: 'compact' | 'full'; // optional prop
}
const props = withDefaults(defineProps<Props>(), {
mode: 'full',
});
</script>
<template>
<div :class="props.mode">
<p>{{ props.name }} is {{ props.age }} years old.</p>
</div>
</template>
Python Comparison
# Python function
def user_card(name: str, age: int, mode: str = 'full'):
return f"{name} is {age} years old ({mode})"
Child-to-Parent Communication
Props go down; events go up.
<!-- Parent -->
<template>
<FileUpload @upload="handleUpload" />
</template>
<script setup lang="ts">
function handleUpload(file: File) {
console.log('Uploaded:', file.name);
}
</script>
The child component calls emit('upload', file) when something happens. The parent decides what to do.
Lifecycle Hooks
Code that runs at specific moments in a component's life.
onMounted: When Component Appears
import { onMounted } from 'vue';
onMounted(async () => {
console.log('Component is on screen');
await fetchData();
});
This is like __post_init__ or a setup function that runs after initialization.
Other Common Hooks
| Hook | When It Runs |
|---|---|
onMounted |
Component is added to DOM |
onUnmounted |
Component is removed from DOM |
onUpdated |
Component re-renders |
onBeforeMount |
Just before first render |
onBeforeUnmount |
Just before removal |
Composables: Reusable Logic
A composable is a reusable function containing Vue logic.
Example: useCounter
// useCounter.ts
export function useCounter() {
const count = ref(0);
function increment() {
count.value += 1;
}
return { count, increment };
}
<!-- Using it -->
<script setup lang="ts">
import { useCounter } from './useCounter';
const { count, increment } = useCounter();
</script>
Why Composables?
Without composables, components get too big. Composables let you:
- Reuse logic across components
- Separate data fetching from UI
- Keep components readable
Real Examples from Projects
// Separate backend logic from UI
const { downloadFlexData } = useJsonDownload();
const rows = await downloadFlexData(payload);
// Authentication logic
const { isAuthenticated, login, logout } = useAuth();
Python Comparison
Composables are like Python modules with business logic:
# Python
from downloads import download_flex_data
rows = download_flex_data(payload)
# Vue
const { downloadFlexData } = useJsonDownload();
const rows = await downloadFlexData(payload);
A Real-World Example: Filter Flow
This pattern appears frequently in data-driven applications.
The Problem
User selects filter options, then clicks "Apply" to fetch data. Without separating "selected" and "applied" state, every dropdown change would trigger a backend request.
State Structure
// What the user is currently editing
const selectedStart = ref<string | null>(defaultStart);
const selectedEnd = ref<string | null>(defaultEnd);
// What was last applied
const appliedStart = ref<string | null>(defaultStart);
const appliedEnd = ref<string | null>(defaultEnd);
Validation
const isValidRange = computed(() => {
if (!selectedStart.value || !selectedEnd.value) return true;
return new Date(selectedStart.value) <= new Date(selectedEnd.value);
});
Apply Action
async function applyFilters() {
if (!isValidRange.value) return;
appliedStart.value = selectedStart.value;
appliedEnd.value = selectedEnd.value;
await refreshData();
}
Data Flow
User changes dropdown
↓
selectedStart updates (draft state)
↓
Validation recomputes
↓
User clicks Apply
↓
selected → applied (commit)
↓
Build payload
↓
Fetch from backend
↓
Update table data
↓
Table re-renders
Why This Matters
- Draft state: what the user is currently editing (may not be valid)
- Applied state: what the system is actually using
This prevents excessive API calls and ensures exports match what's visible on screen.
Vue Strengths, Weaknesses, and Use Cases
Strengths
| Strength | Description |
|---|---|
| Gentle learning curve | Approachable syntax; good docs; Vue 3 Composition API feels natural |
| Reactivity system | Automatic UI updates without manual DOM manipulation |
| Single-file components | Template, logic, and styles in one file—easy to understand |
| Flexible architecture | Use it for simple widgets or complex SPAs |
| Great tooling | Vite dev server, Vue DevTools, excellent TypeScript support |
| Active ecosystem | Vuetify (Material UI), Nuxt (SSR/SSG), Pinia (state management) |
Weaknesses
| Weakness | Description |
|---|---|
| JavaScript ecosystem | Requires understanding npm, build tools, ES modules |
| Reactivity gotchas | Objects/arrays need special handling; .value confusion |
| Mobile/Web separation | Vue itself is web-only (consider NativeScript or Capacitor for mobile) |
| Smaller ecosystem than React | Fewer third-party libraries |
Typical Use Cases
| Use Case | Example |
|---|---|
| Single-page applications (SPAs) | Dashboards, admin panels, SaaS products |
| Interactive UI components | Data tables with filters, dynamic forms, real-time updates |
| Progressive enhancement | Add Vue to existing apps (like you might use jQuery) |
| Prototyping | Quick MVPs with Vuetify or Tailwind UI components |
Quick Reference
Template Syntax
{{ value }} <!-- interpolation -->
v-model="variable" <!-- two-way binding -->
@click="handler" <!-- click event -->
:prop="value" <!-- bind attribute -->
v-if="condition" <!-- conditional -->
v-for="item in items" <!-- loop -->
Script Basics
import { ref, computed, onMounted } from 'vue';
const variable = ref(initialValue); // reactive state
const derived = computed(() => ...); // derived state
onMounted(() => { ... }); // lifecycle hook
Common Patterns
| Pattern | Code |
|---|---|
| Reading a ref | variable.value in script, variable in template |
| Writing a ref | variable.value = newValue |
| Defining props | const props = defineProps<Props>() |
| Optional props | withDefaults(defineProps<Props>(), { optional: 'default' }) |
| Emitting events | emit('eventName', data) |
Summary
Vue combines:
- Reactivity: UI updates automatically when state changes
- Components: Reusable pieces combining template, logic, and styles
- Composables: Shared logic extracted into testable functions
- Props and Events: Clear data flow between components
The key mental shift from Python:
| Python Thinking | Vue Thinking |
|---|---|
| Call functions to update view | Change state, view updates automatically |
| Classes hold state and methods | Components hold reactive refs and functions |
| Import modules for logic | Import composables for reusable logic |
Further Reading
- Vue 3 Documentation
- Vue School Tutorials
- Vuetify Components (your project's UI framework)
- Project composables:
useJsonDownload,useJsonUpload,useAuth