Files
it-know-how/vue/00-Vue Tutorial.md
T
2026-03-27 12:58:01 +01:00

748 lines
17 KiB
Markdown
Executable File

# Vue.js Introduction for Python Developers
A practical guide to Vue.js, written for developers who know Python.
---
## Table of Contents
1. [What is Vue?](#what-is-vue)
2. [The Big Idea: Reactivity](#the-big-idea-reactivity)
3. [Your First Vue Component](#your-first-vue-component)
4. [Reactive State with `ref`](#reactive-state-with-ref)
5. [Derived State with `computed`](#derived-state-with-computed)
6. [Templates: HTML with Vue Features](#templates-html-with-vue-features)
7. [Two-Way Binding with `v-model`](#two-way-binding-with-v-model)
8. [Events and User Interaction](#events-and-user-interaction)
9. [Components and Props](#components-and-props)
10. [Lifecycle Hooks](#lifecycle-hooks)
11. [Composables: Reusable Logic](#composables-reusable-logic)
12. [A Real-World Example: Filter Flow](#a-real-world-example-filter-flow)
13. [Vue Strengths, Weaknesses, and Use Cases](#vue-strengths-weaknesses-and-use-cases)
14. [Quick Reference](#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
# 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
<!-- 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:
```vue
<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:
1. **What values are reactive state?** → Look for `ref()` and `computed()`
2. **Which template elements use that state?** → Look for `{{ variable }}` and `v-model`
3. **Which functions change that state?** → Look for functions that modify `.value`
4. **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.
```ts
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`:
```ts
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`:
```vue
<template>
<p>{{ count }}</p> <!-- reads count.value -->
<p>{{ username }}</p> <!-- reads username.value -->
<button :disabled="isLoading">Save</button>
</template>
```
### Python Comparison
```python
# Python
count = 0
count += 1
```
```ts
// 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
```vue
<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.
```ts
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`:
```python
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
```ts
// 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
```ts
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:
```vue
<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:
```vue
<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:
```vue
<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:
```vue
<button :disabled="isLoading">Save</button>
<img :src="imageUrl" />
<a :href="profileUrl">View Profile</a>
```
Short for `v-bind:disabled`.
### Class Binding
```vue
<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
```vue
<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 → `email` updates
- Code changes `email` → input display updates
### Select Dropdown
```vue
<v-select
v-model="selectedCountry"
:items="countries"
item-title="title"
item-value="value"
/>
```
```ts
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 users
- `item-value`: what's stored in the variable
---
## Events and User Interaction
### Click Events: `@click`
```vue
<button @click="save">Save</button>
```
```ts
function save() {
console.log('saving...');
}
```
### Other Common Events
```vue
<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
```vue
<button @click="deleteItem(id)">Delete</button>
```
```ts
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
```vue
<!-- 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.
```vue
<!-- 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
# 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.
```vue
<!-- 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
```ts
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
```ts
// useCounter.ts
export function useCounter() {
const count = ref(0);
function increment() {
count.value += 1;
}
return { count, increment };
}
```
```vue
<!-- 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
```ts
// 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
# 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
```ts
// 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
```ts
const isValidRange = computed(() => {
if (!selectedStart.value || !selectedEnd.value) return true;
return new Date(selectedStart.value) <= new Date(selectedEnd.value);
});
```
### Apply Action
```ts
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
```vue
{{ 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
```ts
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](https://vuejs.org/guide/)
- [Vue School Tutorials](https://vueschool.io/)
- [Vuetify Components](https://vuetifyjs.com/) (your project's UI framework)
- Project composables: `useJsonDownload`, `useJsonUpload`, `useAuth`