HTML5-Tutorium: JavaScript: Hello World Vue 04
Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:
| Korrektheit: 3 (zu größeren Teilen überprüft) |
Umfang: 4 (unwichtige Fakten fehlen) |
Quellenangaben: 3 (wichtige Quellen vorhanden) |
Quellenarten: 5 (ausgezeichnet) |
Konformität: 3 (gut) |
Inhalt | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5 | Teil 6 | Vue 1 | Vue 2 | Vue 3 | Vue 4 | Vue 5 | Vue 6
Musterlösung
Git-Repository, git checkout v04 (JavaScript)
Git-Repository (TypeScript), git checkout v04
Anwendungsfälle (Use Cases)
Gegenüber dem ersten, zweiten und dritten Teil des Vue-Tutoriums ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.
Aufgabe
Aufgabe: Modularisieren Sie HelloWorld.vue
Definieren Sie für die Komponente HelloWorld.vue zwei Subkomponenten:
components/section/SectionHello.vuecomponents/section/SectionQuestion.vue
Zuvor sollten Sie zwei wiederverwendbare Hilfskomponenten definieren:
components/form/Button.vuecomponents/form/Textfield.vue
Erstellen eines neuen Projektzweigs
Erstellen Sie einen neuen Projektzweig (branch) innerhalb von hello_world_vue und fügen Sie das Package uuid hinzu:
git checkout v03 # Wechsle in den Branch v03
git checkout -b v04 # Klone v03 in einen neuen Branch v04
npm i uuid
npm i
Erstellen der Stores
StoreSection
Im Section-Store wird gespeichert, welche Section der Single-Page-Anwendung dem Benutzer aktuell präsentiert wird.
// src/store/StoreSection.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
const storeSection =
defineStore
( 'section', // must be unique
() =>
{ const
section = ref(''),
init =
p_section =>
section.value = p_section,
visability =
p_section =>
section.value !== p_section ? 'hidden' : '',
change =
p_section =>
section.value = p_section
return { init, visability, change }
}
)
export default storeSection
StoreGreeting
Im Greeting-Store werden die Daten zur Begrüßung des Benutzers gespeichert.
// src/store/StoreGreeting.js
import { defineStore } from 'pinia'
import { reactive, toRef, computed } from 'vue'
const
storeGreeting =
defineStore
( 'greeting',
() =>
{ const
state =
reactive({ name: '', stranger: 'Stranger', hello: 'Hello, $1!' }),
init =
(p_state = {}) =>
{ Object.assign(state, p_state) },
name = toRef(state, 'name'),
sayHello =
p_name string): string =>
state.hello.replace('$1', p_name),
helloStranger =
computed(() => sayHello(state.stranger)),
hello =
computed(() => sayHello(state.name))
return { init, name, helloStranger, hello }
}
)
export default storeGreeting
Erstellen der Stores in einer Komponente
// src/components/HelloWorld.vue
<script setup>
import { onMounted, onUnmounted } from 'vue'
import ControllerKey from '@/controller/ControllerKey'
import storeSection from '@/store/StoreSection'
import storeGreeting from '@/store/StoreGreeting'
const
section = storeSection(),
greeting = storeGreeting(),
sayHello = () => { section.change('hello') },
controllerKey =
new ControllerKey('Enter', sayHello, 'input_name')
section.init('question')
//greeting.init({ hello: 'Hallo, $1!', stranger: 'Fremder'})
onMounted (() => controllerKey.add())
onUnmounted(() => controllerKey.remove())
</script>
Zum Vergleich noch einmal das Skript ohne Store
// src/components/HelloWorld.vue without store
<script setup >
import { ref, computed, onMounted, onUnmounted } from 'vue'
import ControllerKey from '@/controller/ControllerKey'
const
section = ref('question'),
helloStranger = ref('Hello, Stranger'),
name = ref(''),
hello = computed(() => `Hello, ${name.value}!`),
visability = (p_section: string) =>
p_section !== section.value
? 'hidden'
: '',
sayHello = () => { section.value = 'hello' },
controllerKey =
new ControllerKey('Enter', sayHello, 'input_name')
onMounted (() => controllerKey.add())
onUnmounted(() => controllerKey.remove())
</script>
Anpassung des Templates
Das Template muss angepasst werden. Man muss nun auf die Store-Objekte zugreifen.
// src/components/HelloWorld.vue
<template>
<section id="section_question" v-bind:class="section.visability('question')">
<h1>{{greeting.helloStranger}}</h1>
<form>
<div>
<label for="input_name">What's your name?</label>
<input id="input_name" type="text" autofocus
v-model="greeting.name"
/>
</div>
<div>
<input id="button_reset" type="reset" value="Reset"/>
<input id="button_submit" type="button" value="Say hello"
v-on:click="sayHello"
/>
</div>
</form>
</section>
<section id="section_hello" v-bind:class="section.visability('hello')">
<h1>{{greeting.hello}}</h1>
<p>Welcome to Web Programming!</p>
</section>
</template>
Stores als Klassen
Stores können auch mit Hilfe von Klassen definiert werden. Dazu benötigt man zunächst eine Basisklasse, die von jedem Store wiederverwendet werden kann.
Basisklasse Store
// src/common/Store/Store.js
// cmp. https://medium.com/@mario.brendel1990/vue-3-the-new-store-a7569d4a546f
import { reactive } from 'vue'
class Store
{ #initState
state
reset()
{ if (this.state == null)
{ this.state = reactive({...this.initState}) }
else
{ Object.assign(this.state, this.initState) }
}
init(p_state)
{ Object.assign(this.initState, p_state);
this.reset();
}
constructor(p_state)
{ this.initState = p_state;
this.reset();
}
}
export default Store
Definition der Store-Klasse StoreSection
Nun kann der Section-Store als Klasse realisiert werden.
// src/store/StoreSection.js
import Store from '@/common/store/Store'
class StoreSection extends Store
{ constructor()
{ super({ name: '' }) }
visability(p_section)
{ return this.state.name !== p_section ? 'hidden' : '' }
change(p_section)
{ this.state.name = p_section }
}
const storeSection = new StoreSection();
export default storeSection;
Definition der Store-Klasse StoreGreeting
Den Greeting-Store kann man analog definieren.
// src/store/StoreGreeting.js
import Store from '@/common/store/Store'
import { computed } from 'vue'
class StoreGreeting extends Store
{ constructor(p_init = { hello: 'Hello, $1!', stranger: 'Stranger' })
{ super({ name: '', ...p_init }); }
get name()
{ return this.state.name; }
set name(p_name)
{ this.state.name = p_name; }
#sayHello(p_name)
{ return this.state.hello.replace('$1', p_name); }
get helloStranger()
{ return computed(() => this.#sayHello(this.state.stranger)).value }
get hello()
{ return computed(() => this.#sayHello(this.state.name !== ''
? this.state.name
: this.state.stranger
)
).value
}
}
const stateGreeting = new StoreGreeting()
export default stateGreeting
Störend ist hier, dass man für jedes Attribut wie z. B. name eine Getter- und eine Setter-Methode definieren muss.
Dieses Problem kann man mit einer weiteren Superklasse abmildern.
StoreExtendible
Man definiert die Klasse StoreExtendible, die es ermöglicht, bei der Initialisierung des Stores dynamisch reaktive Getter- und Setter-Methoden hinzuzufügen.
// src/common/Store/StoreExtendible.js
import Store from './Store'
class StoreExtendible extends Store
{ #add(p_properties, p_getter, p_setter)
{ if (typeof p_properties === 'string')
{ Object.defineProperty
( this.constructor.prototype,
p_properties,
{ ...( p_getter ? { get: () => this.state[p_properties] } : {} ),
...( p_setter ? { set: (value) =>
{ if (this.state[p_properties] !== value)
{ this.state[p_properties] = value; }
},
}
: {}
),
configurable: false, // the property cannot be redefined
enumerable: true
},
);
}
else
{ p_properties.forEach(e => this.addGetter(e)) }
}
addGetter(p_properties)
{ this.#add(p_properties, true, false) }
addSetter(p_properties)
{ this.#add(p_properties, false, true) }
addGetterSetter(p_properties)
{ this.#add(p_properties, true, true) }
}
export default StoreExtendible
Verwendung der alternativen Stores in einer Komponente
// src/components/HelloWorld.vue with store class
<script setup >
import { onMounted, onUnmounted } from 'vue'
import ControllerKey from '@/controller/ControllerKey'
import section from '@/store/StoreSectionClass'
//import greeting from '@/store/StoreGreetingClass'
import greeting from '@/store/StoreGreetingClassExtendible'
const
sayHello = () => { section.change('hello') },
controllerKey =
new ControllerKey('Enter', sayHello, 'input_name')
section.init({ name: 'question'})
//greeting.init({ hello: 'Hallo, $1!', stranger: 'Fremder'})
onMounted (() => controllerKey.add())
onUnmounted(() => controllerKey.remove())
</script>
Zum Vergleich: Die Verwendung der zuerst definierten Stores.
// src/components/HelloWorld.vue
<script setup>
import { onMounted, onUnmounted } from 'vue'
import ControllerKey from '@/controller/ControllerKey'
import storeSection from '@/store/StoreSection'
import storeGreeting from '@/store/StoreGreeting'
const
section = storeSection(),
greeting = storeGreeting(),
sayHello = () => { section.change('hello') },
controllerKey =
new ControllerKey('Enter', sayHello, 'input_name')
section.init('question')
//greeting.init({ hello: 'Hallo, $1!', stranger: 'Fremder'})
onMounted (() => controllerKey.add())
onUnmounted(() => controllerKey.remove())
</script>
Fortsetzung des Tutoriums
Sie sollten nun Teil 4 des Vue-Tutoriums bearbeiten. Nun wird die Anwendung modularisiert.
Quellen
- Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)
