Skip to content

Vue.js - Das Progressive JavaScript-Framework

Einführung in Vue.js

Was ist Vue.js?

Vue.js (gesprochen "View") ist:

  • Ein modernes JavaScript-Framework für den Aufbau von Benutzeroberflächen
  • Progressiv - du kannst klein anfangen und bei Bedarf erweitern
  • Reaktiv - die Benutzeroberfläche aktualisiert sich automatisch bei Datenänderungen
  • Einfacher zu lernen als React oder Angular
  • Perfekt für Single-Page Applications (SPAs)

Wichtig: Vue.js macht es einfach, interaktive Web-Apps zu erstellen, ohne sich um DOM-Manipulation kümmern zu müssen!


Warum Vue.js?

Vorteile gegenüber "Vanilla JavaScript":

Mit Vanilla JS (Counter-Beispiel):

javascript
let count = 0;
let element = document.querySelector("#count");

document.querySelector("#plus").addEventListener("click", () => {
    count++;
    element.textContent = count;  // Manuelles DOM-Update
});

Mit Vue.js:

vue
<template>
    <div>
        <p>{{ count }}</p>
        <button @click="count++">+</button>
    </div>
</template>

<script>
export default {
    data() {
        return { count: 0 }
    }
}
</script>

Vue übernimmt automatisch:

  • DOM-Updates wenn sich Daten ändern
  • Event-Handling
  • Komponenten-Struktur

Single File Components (SFC)

Was sind Single File Components?

Eine Single File Component ist eine .vue-Datei, die drei Bereiche enthält:

  1. Template - Das HTML (die Ansicht)
  2. Script - Die Logik (JavaScript)
  3. Style - Das CSS (die Styles)

Alles für eine Komponente in einer Datei!


Grundstruktur einer .vue-Datei

vue
<template>
    <!-- HTML-Code hier -->
    <div>
        <h1>Hallo Vue!</h1>
    </div>
</template>

<script>
// JavaScript-Code hier
export default {
    name: 'MeineKomponente'
}
</script>

<style>
/* CSS-Code hier */
h1 {
    color: blue;
}
</style>

Wichtig: Jeder Bereich ist optional, aber normalerweise brauchst du mindestens Template und Script.


Template-Bereich

Der Template-Bereich enthält das HTML der Komponente:

vue
<template>
    <div class="container">
        <h1>Willkommen</h1>
        <p>Dies ist eine Vue-Komponente</p>
        <button>Klick mich</button>
    </div>
</template>

Wichtig:

  • In Vue 3 kann das Template mehrere Root-Elemente haben
  • In Vue 2 musste es genau ein Root-Element geben

Script-Bereich

Der Script-Bereich enthält die Logik der Komponente:

vue
<script>
export default {
    name: 'MeineKomponente',

    data() {
        return {
            nachricht: 'Hallo Vue!',
            zähler: 0
        }
    },

    methods: {
        klickHandler() {
            console.log('Button geklickt!');
        }
    }
}
</script>

Style-Bereich

Der Style-Bereich enthält das CSS:

vue
<style>
.container {
    padding: 20px;
    background-color: #f0f0f0;
}

h1 {
    color: blue;
}
</style>

Scoped Styles (nur für diese Komponente):

vue
<style scoped>
h1 {
    color: blue;  /* Gilt nur für <h1> in dieser Komponente */
}
</style>

Data - Reaktive Daten

Das data() Property

Die data()-Funktion gibt ein Objekt mit allen reaktiven Daten zurück:

vue
<template>
    <div>
        <p>Name: {{ name }}</p>
        <p>Alter: {{ alter }}</p>
        <p>Ist Student: {{ istStudent }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            name: 'Max',
            alter: 25,
            istStudent: true
        }
    }
}
</script>

Wichtig:

  • Alles in data() ist reaktiv - Änderungen aktualisieren automatisch das Template
  • Immer eine Funktion verwenden, die ein Objekt zurückgibt

Daten im Template verwenden

Text-Interpolation mit :

vue
<template>
    <div>
        <h1>{{ titel }}</h1>
        <p>{{ nachricht }}</p>
        <p>2 + 2 = {{ 2 + 2 }}</p>
        <p>{{ vorname + ' ' + nachname }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            titel: 'Meine App',
            nachricht: 'Willkommen bei Vue!',
            vorname: 'Max',
            nachname: 'Mustermann'
        }
    }
}
</script>

Was funktioniert in :

  • Variablen: {{ name }}
  • Ausdrücke: {{ zahl + 5 }}
  • Ternärer Operator: {{ alter >= 18 ? 'Volljährig' : 'Minderjährig' }}
  • Methoden-Aufrufe: {{ getName() }}

Was nicht funktioniert:

  • Keine if/else-Statements
  • Keine Schleifen
  • Keine Zuweisungen

Template-Syntax

v-bind - Attribute binden

v-bind bindet Daten an HTML-Attribute:

Syntax: v-bind:attribut="wert" oder Kurzform :attribut="wert"

vue
<template>
    <div>
        <img v-bind:src="bildUrl" v-bind:alt="bildBeschreibung">
        <!-- Kurzform: -->
        <img :src="bildUrl" :alt="bildBeschreibung">

        <a :href="linkUrl">{{ linkText }}</a>

        <div :class="klassenName">Inhalt</div>

        <button :disabled="istDeaktiviert">Klick mich</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            bildUrl: 'foto.jpg',
            bildBeschreibung: 'Mein Foto',
            linkUrl: 'https://vuejs.org',
            linkText: 'Vue.js Website',
            klassenName: 'highlight',
            istDeaktiviert: false
        }
    }
}
</script>

v-bind für Klassen

String-Syntax:

vue
<div :class="aktivKlasse"></div>

Object-Syntax (dynamische Klassen):

vue
<template>
    <div :class="{ aktiv: istAktiv, highlight: istHighlighted }">
        Inhalt
    </div>
</template>

<script>
export default {
    data() {
        return {
            istAktiv: true,
            istHighlighted: false
        }
    }
}
</script>

Ergebnis: <div class="aktiv">Inhalt</div>

Array-Syntax:

vue
<div :class="[baseClass, aktivClass]"></div>

v-bind für Styles

Object-Syntax:

vue
<template>
    <div :style="{ color: textColor, fontSize: fontSize + 'px' }">
        Text
    </div>
</template>

<script>
export default {
    data() {
        return {
            textColor: 'red',
            fontSize: 20
        }
    }
}
</script>

Mit data-Objekt:

vue
<template>
    <div :style="styleObject">Text</div>
</template>

<script>
export default {
    data() {
        return {
            styleObject: {
                color: 'red',
                fontSize: '20px',
                fontWeight: 'bold'
            }
        }
    }
}
</script>

v-on - Event-Handling

v-on fügt Event-Listener hinzu:

Syntax: v-on:event="handler" oder Kurzform @event="handler"

vue
<template>
    <div>
        <button v-on:click="count++">{{ count }}</button>
        <!-- Kurzform: -->
        <button @click="count++">{{ count }}</button>

        <button @click="handleClick">Klick mich</button>

        <input @input="handleInput" :value="text">

        <form @submit.prevent="handleSubmit">
            <button type="submit">Absenden</button>
        </form>
    </div>
</template>

<script>
export default {
    data() {
        return {
            count: 0,
            text: ''
        }
    },

    methods: {
        handleClick() {
            console.log('Button geklickt!');
        },

        handleInput(event) {
            this.text = event.target.value;
        },

        handleSubmit() {
            console.log('Formular abgesendet');
        }
    }
}
</script>

Event-Modifiers:

  • .prevent - event.preventDefault()
  • .stop - event.stopPropagation()
  • .once - Event nur einmal auslösen

v-model - Two-Way Data Binding

v-model erstellt eine bidirektionale Datenbindung (besonders für Formular-Inputs):

vue
<template>
    <div>
        <input v-model="name" type="text" placeholder="Name">
        <p>Hallo, {{ name }}!</p>

        <textarea v-model="nachricht"></textarea>
        <p>{{ nachricht }}</p>

        <input v-model="istAktiv" type="checkbox">
        <p>Checkbox: {{ istAktiv }}</p>

        <select v-model="ausgewählt">
            <option>Option 1</option>
            <option>Option 2</option>
            <option>Option 3</option>
        </select>
        <p>Ausgewählt: {{ ausgewählt }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            name: '',
            nachricht: '',
            istAktiv: false,
            ausgewählt: ''
        }
    }
}
</script>

Was macht v-model?

  • Synchronisiert automatisch Input-Wert mit data
  • Änderungen im Input aktualisieren data
  • Änderungen in data aktualisieren Input

v-if, v-else-if, v-else - Bedingte Darstellung

Zeigt Elemente nur unter bestimmten Bedingungen:

vue
<template>
    <div>
        <p v-if="istAngemeldet">Willkommen zurück!</p>
        <p v-else>Bitte melde dich an.</p>

        <div v-if="status === 'loading'">Lädt...</div>
        <div v-else-if="status === 'success'">Erfolgreich!</div>
        <div v-else-if="status === 'error'">Fehler aufgetreten</div>
        <div v-else>Bereit</div>

        <button @click="istAngemeldet = !istAngemeldet">
            Toggle
        </button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            istAngemeldet: false,
            status: 'loading'
        }
    }
}
</script>

Wichtig: v-if entfernt Elemente vollständig aus dem DOM (nicht nur verstecken).


v-show - Element verstecken

v-show versteckt Elemente mit CSS (display: none):

vue
<template>
    <div>
        <p v-show="istSichtbar">Dieser Text kann versteckt werden</p>

        <button @click="istSichtbar = !istSichtbar">Toggle</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            istSichtbar: true
        }
    }
}
</script>

v-if vs v-show:

  • v-if: Element wird aus DOM entfernt/hinzugefügt (höhere Umschaltkosten)
  • v-show: Element bleibt im DOM, wird nur versteckt (höhere initiale Kosten)
  • Nutze v-show für häufiges Umschalten
  • Nutze v-if für seltene Bedingungsänderungen

v-for - Listen rendern

v-for wiederholt Elemente für jedes Element in einem Array:

Array durchlaufen:

vue
<template>
    <div>
        <ul>
            <li v-for="item in liste" :key="item">
                {{ item }}
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            liste: ['Äpfel', 'Bananen', 'Orangen']
        }
    }
}
</script>

Mit Index:

vue
<template>
    <ul>
        <li v-for="(item, index) in liste" :key="index">
            {{ index + 1 }}. {{ item }}
        </li>
    </ul>
</template>

Array von Objekten:

vue
<template>
    <div>
        <div v-for="person in personen" :key="person.id">
            <h3>{{ person.name }}</h3>
            <p>Alter: {{ person.alter }}</p>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            personen: [
                { id: 1, name: 'Max', alter: 25 },
                { id: 2, name: 'Anna', alter: 30 },
                { id: 3, name: 'Tom', alter: 22 }
            ]
        }
    }
}
</script>

Wichtig: Immer :key verwenden für eindeutige Identifizierung!


v-for mit v-if kombinieren

vue
<template>
    <div>
        <div v-for="person in personen" :key="person.id">
            <div v-if="person.alter >= 18">
                {{ person.name }} ({{ person.alter }})
            </div>
        </div>
    </div>
</template>

Best Practice: Lieber im Script filtern:

vue
<template>
    <div>
        <div v-for="person in volljährigePersonen" :key="person.id">
            {{ person.name }} ({{ person.alter }})
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            personen: [
                { id: 1, name: 'Max', alter: 25 },
                { id: 2, name: 'Anna', alter: 16 },
                { id: 3, name: 'Tom', alter: 30 }
            ]
        }
    },

    computed: {
        volljährigePersonen() {
            return this.personen.filter(p => p.alter >= 18);
        }
    }
}
</script>

Methods - Methoden

Methoden definieren

Methoden sind Funktionen, die in der Komponente verwendet werden:

vue
<template>
    <div>
        <button @click="grüßen">Grüßen</button>
        <button @click="addieren(5, 3)">5 + 3</button>
        <p>{{ getFullName() }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            vorname: 'Max',
            nachname: 'Mustermann'
        }
    },

    methods: {
        grüßen() {
            alert('Hallo!');
        },

        addieren(a, b) {
            console.log(a + b);
        },

        getFullName() {
            return this.vorname + ' ' + this.nachname;
        }
    }
}
</script>

Wichtig: Auf data mit this zugreifen!


Methoden mit Parametern

vue
<template>
    <div>
        <button @click="grüßePerson('Max')">Max grüßen</button>
        <button @click="grüßePerson('Anna')">Anna grüßen</button>

        <input @input="logInput($event)">
    </div>
</template>

<script>
export default {
    methods: {
        grüßePerson(name) {
            alert(`Hallo, ${name}!`);
        },

        logInput(event) {
            console.log(event.target.value);
        }
    }
}
</script>

Computed Properties

Was sind Computed Properties?

Computed Properties sind berechnete Eigenschaften, die:

  • Von anderen Daten abhängen
  • Gecacht werden (nur neu berechnet bei Datenänderung)
  • Wie normale Properties verwendet werden (kein Funktionsaufruf)
vue
<template>
    <div>
        <p>Vorname: {{ vorname }}</p>
        <p>Nachname: {{ nachname }}</p>
        <p>Vollständiger Name: {{ vollständigerName }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            vorname: 'Max',
            nachname: 'Mustermann'
        }
    },

    computed: {
        vollständigerName() {
            return this.vorname + ' ' + this.nachname;
        }
    }
}
</script>

Computed vs Methods

Mit Method:

vue
<p>{{ getFullName() }}</p>  <!-- Wird bei jedem Render ausgeführt -->

Mit Computed:

vue
<p>{{ fullName }}</p>  <!-- Wird nur bei Datenänderung neu berechnet -->

Wann was verwenden:

  • Computed: Für berechnete Werte, die von anderen Daten abhängen
  • Methods: Für Aktionen und Event-Handler

Praktisches Beispiel

vue
<template>
    <div>
        <h2>Einkaufsliste</h2>

        <input v-model="neuerArtikel" @keyup.enter="hinzufügen">
        <button @click="hinzufügen">Hinzufügen</button>

        <p>Anzahl Artikel: {{ artikelAnzahl }}</p>

        <ul>
            <li v-for="(artikel, index) in artikel" :key="index">
                {{ artikel }}
                <button @click="entfernen(index)">X</button>
            </li>
        </ul>

        <p v-if="istLeer">Liste ist leer</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            neuerArtikel: '',
            artikel: []
        }
    },

    computed: {
        artikelAnzahl() {
            return this.artikel.length;
        },

        istLeer() {
            return this.artikel.length === 0;
        }
    },

    methods: {
        hinzufügen() {
            if (this.neuerArtikel.trim() !== '') {
                this.artikel.push(this.neuerArtikel);
                this.neuerArtikel = '';
            }
        },

        entfernen(index) {
            this.artikel.splice(index, 1);
        }
    }
}
</script>

<style scoped>
li {
    margin: 10px 0;
}

button {
    margin-left: 10px;
}
</style>

Komplettes Beispiel: Counter-App

vue
<template>
    <div class="counter">
        <h1>Counter App</h1>

        <div class="display">
            <h2 :class="{ negative: count < 0, positive: count > 0 }">
                {{ count }}
            </h2>
        </div>

        <div class="controls">
            <button @click="decrement">-</button>
            <button @click="reset">Reset</button>
            <button @click="increment">+</button>
        </div>

        <div class="input-group">
            <input
                v-model.number="step"
                type="number"
                placeholder="Schrittweite"
            >
            <button @click="addCustom">Hinzufügen</button>
        </div>

        <p>Status: {{ status }}</p>
    </div>
</template>

<script>
export default {
    name: 'CounterApp',

    data() {
        return {
            count: 0,
            step: 1
        }
    },

    computed: {
        status() {
            if (this.count === 0) return 'Neutral';
            if (this.count > 0) return 'Positiv';
            return 'Negativ';
        }
    },

    methods: {
        increment() {
            this.count++;
        },

        decrement() {
            this.count--;
        },

        reset() {
            this.count = 0;
        },

        addCustom() {
            this.count += this.step;
        }
    }
}
</script>

<style scoped>
.counter {
    max-width: 400px;
    margin: 50px auto;
    padding: 30px;
    background-color: #f8f9fa;
    border-radius: 10px;
    text-align: center;
}

.display {
    margin: 30px 0;
}

.display h2 {
    font-size: 72px;
    margin: 0;
    transition: color 0.3s;
}

.negative {
    color: #dc3545;
}

.positive {
    color: #28a745;
}

.controls {
    display: flex;
    gap: 10px;
    justify-content: center;
    margin-bottom: 20px;
}

button {
    padding: 10px 20px;
    font-size: 18px;
    border: none;
    border-radius: 5px;
    background-color: #007bff;
    color: white;
    cursor: pointer;
    transition: background-color 0.3s;
}

button:hover {
    background-color: #0056b3;
}

.input-group {
    display: flex;
    gap: 10px;
    justify-content: center;
    margin-top: 20px;
}

input {
    padding: 10px;
    font-size: 16px;
    border: 1px solid #ccc;
    border-radius: 5px;
    width: 150px;
}
</style>

Komplettes Beispiel: Todo-Liste

vue
<template>
    <div class="todo-app">
        <h1>Todo Liste</h1>

        <div class="input-section">
            <input
                v-model="neueAufgabe"
                @keyup.enter="hinzufügen"
                type="text"
                placeholder="Neue Aufgabe..."
            >
            <button @click="hinzufügen">Hinzufügen</button>
        </div>

        <div class="filter-section">
            <button
                :class="{ active: filter === 'alle' }"
                @click="filter = 'alle'"
            >
                Alle ({{ todos.length }})
            </button>
            <button
                :class="{ active: filter === 'offen' }"
                @click="filter = 'offen'"
            >
                Offen ({{ offenAnzahl }})
            </button>
            <button
                :class="{ active: filter === 'erledigt' }"
                @click="filter = 'erledigt'"
            >
                Erledigt ({{ erledigtAnzahl }})
            </button>
        </div>

        <ul class="todo-list">
            <li
                v-for="todo in gefilterteTodos"
                :key="todo.id"
                :class="{ completed: todo.erledigt }"
            >
                <input
                    type="checkbox"
                    v-model="todo.erledigt"
                >
                <span>{{ todo.text }}</span>
                <button @click="entfernen(todo.id)" class="delete">

                </button>
            </li>
        </ul>

        <p v-if="todos.length === 0" class="empty">
            Keine Aufgaben vorhanden. Füge eine hinzu!
        </p>
    </div>
</template>

<script>
export default {
    name: 'TodoApp',

    data() {
        return {
            neueAufgabe: '',
            filter: 'alle',
            nextId: 1,
            todos: []
        }
    },

    computed: {
        gefilterteTodos() {
            if (this.filter === 'offen') {
                return this.todos.filter(t => !t.erledigt);
            }
            if (this.filter === 'erledigt') {
                return this.todos.filter(t => t.erledigt);
            }
            return this.todos;
        },

        offenAnzahl() {
            return this.todos.filter(t => !t.erledigt).length;
        },

        erledigtAnzahl() {
            return this.todos.filter(t => t.erledigt).length;
        }
    },

    methods: {
        hinzufügen() {
            const text = this.neueAufgabe.trim();

            if (text !== '') {
                this.todos.push({
                    id: this.nextId++,
                    text: text,
                    erledigt: false
                });
                this.neueAufgabe = '';
            }
        },

        entfernen(id) {
            this.todos = this.todos.filter(t => t.id !== id);
        }
    }
}
</script>

<style scoped>
.todo-app {
    max-width: 600px;
    margin: 50px auto;
    padding: 30px;
    background-color: white;
    border-radius: 10px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

h1 {
    text-align: center;
    color: #333;
}

.input-section {
    display: flex;
    gap: 10px;
    margin-bottom: 20px;
}

.input-section input {
    flex: 1;
    padding: 12px;
    font-size: 16px;
    border: 1px solid #ddd;
    border-radius: 5px;
}

.input-section button {
    padding: 12px 24px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 16px;
}

.input-section button:hover {
    background-color: #0056b3;
}

.filter-section {
    display: flex;
    gap: 10px;
    margin-bottom: 20px;
}

.filter-section button {
    flex: 1;
    padding: 10px;
    background-color: #f0f0f0;
    border: 1px solid #ddd;
    border-radius: 5px;
    cursor: pointer;
}

.filter-section button.active {
    background-color: #007bff;
    color: white;
    border-color: #007bff;
}

.todo-list {
    list-style: none;
    padding: 0;
}

.todo-list li {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px;
    border: 1px solid #ddd;
    border-radius: 5px;
    margin-bottom: 10px;
    transition: background-color 0.3s;
}

.todo-list li:hover {
    background-color: #f8f9fa;
}

.todo-list li.completed span {
    text-decoration: line-through;
    color: #999;
}

.todo-list input[type="checkbox"] {
    cursor: pointer;
    width: 20px;
    height: 20px;
}

.todo-list span {
    flex: 1;
}

.delete {
    background-color: #dc3545;
    color: white;
    border: none;
    border-radius: 3px;
    padding: 5px 10px;
    cursor: pointer;
}

.delete:hover {
    background-color: #c82333;
}

.empty {
    text-align: center;
    color: #999;
    margin-top: 20px;
}
</style>

Wichtige Konzepte zusammengefasst

Reaktivität

Vue überwacht automatisch Änderungen in data():

javascript
data() {
    return {
        count: 0  // Reaktiv!
    }
}

Bei Änderung wird das Template automatisch aktualisiert:

javascript
this.count++;  // Template wird neu gerendert

this-Keyword

In Methods und Computed Properties bezieht sich this auf die Komponenten-Instanz:

javascript
export default {
    data() {
        return {
            name: 'Max'
        }
    },

    methods: {
        grüßen() {
            console.log(this.name);  // "Max"
        }
    }
}

Vorsicht bei Arrow Functions:

javascript
methods: {
    // Gut
    grüßen() {
        console.log(this.name);  // Funktioniert
    },

    // Schlecht
    grüßen: () => {
        console.log(this.name);  // this ist undefined!
    }
}

Lifecycle Hooks (Lebenszyklus)

Vue-Komponenten haben einen Lebenszyklus mit Hooks:

vue
<script>
export default {
    data() {
        return {
            daten: null
        }
    },

    created() {
        // Wird aufgerufen, wenn Komponente erstellt wurde
        console.log('Komponente erstellt');
    },

    mounted() {
        // Wird aufgerufen, wenn Komponente ins DOM eingefügt wurde
        console.log('Komponente gemountet');
        // Ideal für API-Calls
        this.ladeDaten();
    },

    methods: {
        ladeDaten() {
            // Daten laden...
        }
    }
}
</script>

Wichtigste Hooks:

  • created() - Nach Erstellung der Komponente
  • mounted() - Nach Einfügen ins DOM
  • updated() - Nach jedem Update
  • unmounted() - Vor Entfernung aus DOM

Best Practices

1. Aussagekräftige Komponentennamen

javascript
// Gut
export default {
    name: 'TodoList'
}

// Schlecht
export default {
    name: 'Comp1'
}

2. Data immer als Funktion

javascript
// Richtig
data() {
    return {
        count: 0
    }
}

// Falsch
data: {
    count: 0
}

3. Key bei v-for verwenden

vue
<!-- Gut -->
<div v-for="item in items" :key="item.id">

<!-- Schlecht -->
<div v-for="item in items">

4. Computed für berechnete Werte

javascript
// Gut
computed: {
    fullName() {
        return this.firstName + ' ' + this.lastName;
    }
}

Schlecht (im Template): {{ firstName + ' ' + lastName }}


5. Scoped Styles verwenden

vue
<style scoped>
/* Gilt nur für diese Komponente */
.button {
    color: red;
}
</style>

Debugging-Tipps

Vue DevTools

Installiere die Vue DevTools Browser-Extension:

  • Chrome: Vue.js devtools
  • Firefox: Vue.js devtools

Features:

  • Komponenten-Baum ansehen
  • Data und Props inspizieren
  • Events verfolgen
  • Vuex-State ansehen (fortgeschritten)

console.log() in Methods

javascript
methods: {
    handleClick() {
        console.log('Count:', this.count);
        console.log('Data:', this.$data);
    }
}

Template mit

vue
<template>
    <div>
        <p>Debug: {{ $data }}</p>
        <p>Count: {{ count }}</p>
    </div>
</template>

Häufige Fehler

1. this vergessen

javascript
// Falsch
methods: {
    increment() {
        count++;  // Fehler!
    }
}

// Richtig
methods: {
    increment() {
        this.count++;
    }
}

2. Arrow Functions in methods/computed

javascript
// Falsch
methods: {
    greet: () => {
        console.log(this.name);  // this ist undefined!
    }
}

// Richtig
methods: {
    greet() {
        console.log(this.name);
    }
}

3. Computed Properties mit ()

vue
```vue
<!-- Falsch -->
<p>{{ fullName() }}</p>

<!-- Richtig -->
<p>{{ fullName }}</p>

---

### 4. v-for ohne :key

```vue
<!-- Falsch -->
<div v-for="item in items">

<!-- Richtig -->
<div v-for="item in items" :key="item.id">

Ressourcen zum Weiterlernen

Offizielle Dokumentation:

Interaktive Kurse:

Video-Tutorials:

Community:


Cheat Sheet

KonzeptSyntaxBeispiel
Interpolation{{ name }}
Bind Attribute:attr:src="bildUrl"
Event@event@click="handler"
Two-Way Bindingv-modelv-model="text"
Conditionalv-ifv-if="isActive"
Loopv-forv-for="item in items" :key="item.id"
Computedcomputed: {}computed: { fullName() {} }
Methodmethods: {}methods: { greet() {} }

HTML-Templates zum Kopieren

Einfaches Starter-Template

Dieses Template kannst du als Ausgangspunkt für Vue.js-Projekte verwenden:

html
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue.js App</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: Arial, sans-serif;
            padding: 20px;
            background-color: #f5f5f5;
        }

        #app {
            max-width: 800px;
            margin: 0 auto;
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }

        h1 {
            color: #42b883;
            margin-bottom: 20px;
        }

        button {
            background-color: #42b883;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
        }

        button:hover {
            background-color: #35495e;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>{{ title }}</h1>
        <p>{{ message }}</p>
        <button @click="handleClick">Klick mich!</button>
    </div>

    <!-- Vue.js über CDN einbinden -->
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

    <script>
        const { createApp } = Vue;

        createApp({
            data() {
                return {
                    title: 'Meine Vue.js App',
                    message: 'Hallo Vue!'
                }
            },

            methods: {
                handleClick() {
                    this.message = 'Button wurde geklickt!';
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

Counter-App Template

Ein vollständiges Counter-Beispiel:

html
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue Counter App</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }

        #app {
            background-color: white;
            padding: 40px;
            border-radius: 20px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            text-align: center;
            min-width: 300px;
        }

        h1 {
            color: #333;
            margin-bottom: 30px;
        }

        .counter-display {
            font-size: 72px;
            font-weight: bold;
            margin: 30px 0;
            transition: color 0.3s;
        }

        .counter-display.positive {
            color: #28a745;
        }

        .counter-display.negative {
            color: #dc3545;
        }

        .counter-display.zero {
            color: #333;
        }

        .button-group {
            display: flex;
            gap: 10px;
            justify-content: center;
            margin-bottom: 20px;
        }

        button {
            background-color: #667eea;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 8px;
            cursor: pointer;
            font-size: 18px;
            font-weight: bold;
            transition: all 0.3s;
        }

        button:hover {
            background-color: #764ba2;
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        }

        button:active {
            transform: translateY(0);
        }

        .status {
            margin-top: 20px;
            font-size: 18px;
            color: #666;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>Counter App</h1>

        <div
            class="counter-display"
            :class="{
                positive: count > 0,
                negative: count < 0,
                zero: count === 0
            }"
        >
            {{ count }}
        </div>

        <div class="button-group">
            <button @click="decrement">-</button>
            <button @click="reset">Reset</button>
            <button @click="increment">+</button>
        </div>

        <div class="status">
            Status: {{ status }}
        </div>
    </div>

    <!-- Vue.js über CDN einbinden -->
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

    <script>
        const { createApp } = Vue;

        createApp({
            data() {
                return {
                    count: 0
                }
            },

            computed: {
                status() {
                    if (this.count > 0) return 'Positiv';
                    if (this.count < 0) return 'Negativ';
                    return 'Neutral';
                }
            },

            methods: {
                increment() {
                    this.count++;
                },

                decrement() {
                    this.count--;
                },

                reset() {
                    this.count = 0;
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

Todo-Liste Template

Ein vollständiges Todo-Listen-Beispiel:

html
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue Todo Liste</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: Arial, sans-serif;
            background-color: #f5f5f5;
            padding: 20px;
        }

        #app {
            max-width: 600px;
            margin: 0 auto;
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        }

        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 30px;
        }

        .input-group {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
        }

        input[type="text"] {
            flex: 1;
            padding: 12px;
            border: 2px solid #ddd;
            border-radius: 5px;
            font-size: 16px;
        }

        input[type="text"]:focus {
            outline: none;
            border-color: #42b883;
        }

        .btn {
            padding: 12px 24px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            font-weight: bold;
            transition: all 0.3s;
        }

        .btn-add {
            background-color: #42b883;
            color: white;
        }

        .btn-add:hover {
            background-color: #35495e;
        }

        .todo-list {
            list-style: none;
        }

        .todo-item {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 5px;
            margin-bottom: 10px;
            transition: background-color 0.3s;
        }

        .todo-item:hover {
            background-color: #f8f9fa;
        }

        .todo-item.completed {
            opacity: 0.6;
        }

        .todo-item.completed .todo-text {
            text-decoration: line-through;
            color: #999;
        }

        input[type="checkbox"] {
            width: 20px;
            height: 20px;
            cursor: pointer;
        }

        .todo-text {
            flex: 1;
            font-size: 16px;
        }

        .btn-delete {
            background-color: #dc3545;
            color: white;
            padding: 6px 12px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 14px;
        }

        .btn-delete:hover {
            background-color: #c82333;
        }

        .stats {
            margin-top: 20px;
            padding-top: 20px;
            border-top: 2px solid #ddd;
            text-align: center;
            color: #666;
        }

        .empty-state {
            text-align: center;
            color: #999;
            padding: 40px;
            font-style: italic;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>📝 Todo Liste</h1>

        <div class="input-group">
            <input
                v-model="newTodo"
                @keyup.enter="addTodo"
                type="text"
                placeholder="Neue Aufgabe eingeben..."
            >
            <button @click="addTodo" class="btn btn-add">
                Hinzufügen
            </button>
        </div>

        <ul class="todo-list" v-if="todos.length > 0">
            <li
                v-for="todo in todos"
                :key="todo.id"
                class="todo-item"
                :class="{ completed: todo.completed }"
            >
                <input
                    type="checkbox"
                    v-model="todo.completed"
                >
                <span class="todo-text">{{ todo.text }}</span>
                <button
                    @click="deleteTodo(todo.id)"
                    class="btn-delete"
                >

                </button>
            </li>
        </ul>

        <div v-else class="empty-state">
            Keine Aufgaben vorhanden. Füge deine erste Aufgabe hinzu!
        </div>

        <div class="stats" v-if="todos.length > 0">
            <strong>{{ remainingCount }}</strong> von <strong>{{ totalCount }}</strong> Aufgaben offen
        </div>
    </div>

    <!-- Vue.js über CDN einbinden -->
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

    <script>
        const { createApp } = Vue;

        createApp({
            data() {
                return {
                    newTodo: '',
                    todos: [],
                    nextId: 1
                }
            },

            computed: {
                totalCount() {
                    return this.todos.length;
                },

                remainingCount() {
                    return this.todos.filter(t => !t.completed).length;
                }
            },

            methods: {
                addTodo() {
                    const text = this.newTodo.trim();

                    if (text !== '') {
                        this.todos.push({
                            id: this.nextId++,
                            text: text,
                            completed: false
                        });
                        this.newTodo = '';
                    }
                },

                deleteTodo(id) {
                    this.todos = this.todos.filter(t => t.id !== id);
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

Leeres Template zum Experimentieren

Ein minimales Template zum Ausprobieren:

html
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue.js Playground</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 40px;
            max-width: 800px;
            margin: 0 auto;
        }

        h1 {
            color: #42b883;
        }

        /* Deine Styles hier */
    </style>
</head>
<body>
    <div id="app">
        <h1>{{ title }}</h1>

        <!-- Dein HTML hier -->

    </div>

    <!-- Vue.js über CDN einbinden -->
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

    <script>
        const { createApp } = Vue;

        createApp({
            data() {
                return {
                    title: 'Vue.js Playground'
                    // Deine Daten hier
                }
            },

            computed: {
                // Deine Computed Properties hier
            },

            methods: {
                // Deine Methoden hier
            }
        }).mount('#app');
    </script>
</body>
</html>

Hinweise zur Verwendung

CDN vs. Build-Tools

Diese Templates verwenden Vue.js per CDN:

  • ✓ Perfekt zum Lernen und Experimentieren
  • ✓ Keine Installation nötig
  • ✓ Alles in einer Datei
  • ✗ Nicht für große Produktions-Apps geeignet
  • ✗ Keine Single File Components (.vue Dateien)

Für größere Projekte:

  • Verwende Vue CLI oder Vite
  • Nutze Single File Components
  • Profitiere von Build-Optimierung

So verwendest du die Templates:

  1. Kopiere den gewünschten Template-Code
  2. Erstelle eine neue .html Datei
  3. Füge den Code ein
  4. Öffne die Datei im Browser
  5. Beginne zu experimentieren!
html
<!-- Vue 3 (neueste Version) -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<!-- Vue 3 (spezifische Version) -->
<script src="https://unpkg.com/vue@3.3.4/dist/vue.global.js"></script>

<!-- Vue 3 (Production Build - minified) -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

Viel Erfolg beim Erstellen deiner Vue.js Apps!

Informatik & ICT Unterricht Neufeld