HTML5-Tutorium: JavaScript: Hello World Vue 03: Unterschied zwischen den Versionen
Kowa (Diskussion | Beiträge) |
Kowa (Diskussion | Beiträge) |
||
Zeile 56: | Zeile 56: | ||
</source> | </source> | ||
== | == Erstellen der Stores == | ||
=== <code>StoreSection</code> === | === <code>StoreSection</code> === | ||
Zeile 136: | Zeile 136: | ||
export default storeGreeting | export default storeGreeting | ||
</source> | |||
== Erstellen der Stores in einer Komponente== | |||
<source lang="ecmascript"> | |||
// 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> | |||
</source> | |||
Zum Vergleich noch einmal das Skript ohne Store | |||
<source lang="ecmascript"> | |||
// 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> | |||
</source> | |||
== Anpassung des Templates == | |||
Das Template muss angepasst werden. Man muss nun auf die Store-Objekte zugreifen. | |||
<source lang="ecmascript"> | |||
// 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> | |||
</source> | |||
== 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 <code>Store</code> === | |||
<source lang="ecmascript"> | |||
// 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 | |||
</source> | |||
=== Definition der Store-Klasse StoreSection === | |||
Nun kann der Section-Store als Klasse realisiert werden. | |||
<source lang="ecmascript"> | |||
// 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; | |||
</source> | |||
=== Definition der Store-Klasse StoreGreeting === | |||
Den Greeting-Store kann man analog definieren. | |||
<source lang="ecmascript"> | |||
// 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 | |||
</source> | |||
Störend ist hier, dass man für jedes Attribut wie {{zB}} <code>name</code> eine Getter- und eine Setter-Methode definieren muss. | |||
Dieses Problem kann man mit einer weiteren Superklasse abmildern. | |||
=== StoreExtendible === | |||
Man definiert die Klasse <code>StoreExtendible</code>, die es ermöglicht, bei der Initialisierung des Stores dynamisch reaktive Getter- und Setter-Methoden hinzuzufügen. | |||
<source lang="ecmascript"> | |||
// 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 | |||
</source> | |||
<source lang="ecmascript"> | |||
</source> | |||
<source lang="ecmascript"> | |||
</source> | </source> | ||
Version vom 18. April 2023, 10:33 Uhr
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 v03
(JavaScript)
Git-Repository (TypeScript), git checkout v03
Stores mit Hilfe von Klassen:
Git-Repository, git checkout v03b
(JavaScript)
Git-Repository (TypeScript), git checkout v03b
Anwendungsfälle (Use Cases)
Gegenüber dem ersten und zweiten Teil des Vue-Tutoriums ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.
Aufgabe
Aufgabe: Speichere den Zustand der Anwendung (Model) in Store-Objekten, um die künftige Modularisierung (Version v04) zu unterstützen.
In diesem Tutorium sollen die Daten in einem Store außerhalb der Komponenten gespeichert werden.
Mögliche Storekonzepte
- vuex (veraltet, wird nur noch gewartet, angeblich nicht mehr weiterentwickelt, aber Version 4 existiert)
- Pinia (der Nachfolger von vuex; einfacher zu benutzen; Vue-3- und Vue-2-Paradigmen werden unterstützt)
- ohne Bibliothek direkt mit
ref
,reactive
undcomputed
aus Vue 3.2 (vgl. Vue 3 — The New Store)
Wir verwenden Pinia.
Pinia
Zitat:
While our hand-rolled state management solution will suffice in simple scenarios, there are many more things to consider in large-scale production applications:
- Stronger conventions for team collaboration
- Integrating with the Vue DevTools, including timeline, in-component inspection, and time-travel debugging
- Hot Module Replacement
- Server-Side Rendering support
Pinia is a state management library that implements all of the above. It is maintained by the Vue core team, and works with both Vue 2 and Vue 3.
Existing users may be familiar with Vuex, the previous official state management library for Vue. With Pinia serving the same role in the ecosystem, Vuex is now in maintenance mode. It still works, but will no longer receive new features. It is recommended to use Pinia for new applications.
Pinia started out as an exploration of what the next iteration of Vuex could look like, incorporating many ideas from core team discussions for Vuex 5. Eventually, we realized that Pinia already implements most of what we wanted in Vuex 5, and decided to make it the new recommendation instead.
Compared to Vuex, Pinia provides a simpler API with less ceremony, offers Composition-API-style APIs, and most importantly, has solid type inference support when used with TypeScript.
Erstellen eines neuen Projektzweigs
Erstellen Sie einen neuen Projektzweig (branch) innerhalb von hello_world_vue
:
git checkout v02 # Wechsle in den Branch v02
git checkout -b v03 # Klone v02 in einen neuen Branch v03
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
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)