HTML5-Tutorium: JavaScript: Hello World Vue 03

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg

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)

Vorlesung WebProg

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 und computed 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:

  1. Stronger conventions for team collaboration
  2. Integrating with the Vue DevTools, including timeline, in-component inspection, and time-travel debugging
  3. Hot Module Replacement
  4. 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
npm i

Initialisierung von Pinia

Pinia muss als reguläre Dependency installiert werden, da das Paket in den produktiven Code integriert wird.

npm i pinia  // NICHT  npm i -D pinia

Pinia wird als Plugin in die App integriert. Dabei ist wichtig, dass das Pina-Plugin-Objekt vor dem eigentlichen App-Objekt erzeugt wird, da Komponenten der App, die Pinia verwenden, darauf angewiesen sind, dass die Pinia-Erzeugung vor der Komponenten-Initialisierung erfolgt.

// src/main.js

import { createPinia } from 'pinia'
import { createApp }   from 'vue'
import App             from './App.vue'

const pinia = createPinia() // before createApp(...)

window.addEventListener
( 'load'
  createApp(App).use(pinia).mount('#app')
)

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
      currentSection =
        ref(''),
   
      init =
        p_section =>
        currentSection.value = p_section,

      visability =
        p_section =>
        currentSection.value !== p_section ? 'hidden' : '',

      change =
        init // change macht dasselbe wie init

    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 => state.hello.replace('$1', p_name),

      helloStranger =
        computed(() => sayHello(state.stranger)),

      hello =
        computed(() => sayHello(state.name))

      return { init, name, helloStranger, hello }
    }
)

export default storeGreeting

Einbinden der Stores in einer Komponente

<!-- src/components/HelloWorld.vue -->

<script setup>
  import { onMounted, onUnmounted } from 'vue'
  import storeSection  from '@/store/StoreSection'
  import storeGreeting from '@/store/StoreGreeting'
  import ControllerKey from '@/controller/ControllerKey'

  const
    section  = storeSection(),
    greeting = storeGreeting(),

    sayHello      = () => { section.change('hello') },
    controllerKey = new ControllerKey('Enter', sayHello, 'input_name')

  section.init('form')
//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('form'),
    helloStranger = ref('Hello, Stranger'),
    name          = ref(''),

    hello         = computed(() => `Hello, ${name.value}!`),
    visability    = p_section =>
                    p_section !== section.value ? 'hidden' : '',
    sayHello      = () => { section.value = 'hello' },
    autofocus     = () => { document.getElementById('input_name').focus() },
    controllerKey = new ControllerKey('Enter', sayHello, 'input_name')


  onMounted  (...)
  onUnmounted(...)
</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 is 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>


<script setup >

 import { onMounted, onUnmounted } from 'vue'
 import section  from '@/store/StoreSectionClass'

//import greeting from '@/store/StoreGreetingClass'

 import greeting from '@/store/StoreGreetingClassExtendible'
 import ControllerKey from '@/controller/ControllerKey'
 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> </source>

Zum Vergleich: Die Verwendung der zuerst definierten Stores.

<!-- src/components/HelloWorld.vue -->

<script setup>
  import { onMounted, onUnmounted } from 'vue'
  import storeSection  from '@/store/StoreSection'
  import storeGreeting from '@/store/StoreGreeting'
  import ControllerKey from '@/controller/ControllerKey'

  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

  1. Kowarschick (WebProg): Wolfgang Kowarschick; Vorlesung „Web-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2024; Quellengüte: 3 (Vorlesung)