Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Vuex: esempio di applicazione

Mettere in pratica i concetti relativi a Vuex, realizzando un'app di esempio, in grado di mantenere lo stato, mediante Vue.js.
Mettere in pratica i concetti relativi a Vuex, realizzando un'app di esempio, in grado di mantenere lo stato, mediante Vue.js.
Link copiato negli appunti

Dopo la parentesi teorica della lezione precedente, nel seguito implementeremo da zero una piccola applicazione di checklist dove sarà possibile segnare come completate attività appartenenti a diversi gruppi.

Per capire meglio che applicazione implementeremo, ecco uno screenshot:

Figura 1. Screenshot dell'applicazione (click per ingrandire)

Screenshot dell'applicazione

Il codice sorgente è inoltre allegati a questo articolo.

L'applicazione è implementata tramite file Vue, compilati grazie Babel e Webpack, scaricati tramite Yarn.

Store

Il cuore dell'applicazione è lo store, che incapsula il modello dati reso disponibile da Vuex nei vari componenti. Esso viene definito all'interno del file src/vuex/store.js.

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
function findItemByIds(state, checklistId, itemId) {
    let checklist = state.checklists.find(checklist => checklist.id === checklistId);
    if(!checklist) return null;
    return checklist.items.find(item => item.id === itemId);
}
export default new Vuex.Store({
    state: {
        checklists: [{
            id: 1,
            name: 'Wishlist',
            items: [{
                id: 1,
                name: 'TV 50 inches',
                done: false
            }, {
   	        ....
	    }]
	} , {
	    .....
    	}]
    },
    getters: {
        checklistCount(state) {
            return state.checklists.length;
        },
        completedChecklistCount(state) {
            return state.checklists.filter(checklist => {
                return checklist.items.every(item => item.done);
            }).length;
        },
        itemsCount(state) {
            return state.checklists.reduce((count, checklist) => {
                return count + checklist.items.length;
            }, 0);
        },
        completedItemsCount(state) {
            return state.checklists.reduce((count, checklist) => {
                return checklist.items.reduce((count, item) => {
                    return count + (item.done ? 1 : 0);
                }, count);
            }, 0);
        }
    },
    mutations: {
        toggle(state, payload) {
            let item = findItemByIds(state, payload.checklistId, payload.itemId);
            if(item) {
                item.done = !item.done;
            }
        }
    },
    actions: {
        toggle(context, payload) {
            context.commit('toggle', payload);
        }
    }
});

Il file parte con l'inclusione di Vue e di Vuex, con l'installazone di Vuex tramite Vue.use e con la definizione di una funzione privata utilizzata in una mutation. Successivamente viene inizializzato l'oggetto Vuex.Store a partire da un set di opzioni:

  • state: rappresenta il modello dati iniziale dell'applicazione. Nel nostro esempio, per semplicità, esso viene scritto a mano all'interno del file .js, ma in un contesto reale esso potrebbe arrivare dal server tramite una API dedicata;
  • getters: sono funzioni specifiche che permettono di ritornare informazioni complesse a partire dallo state. I getters vengono definiti direttamente all'interno dello store in modo da poter essere riutilizzati. In particolare abbiamo quattro getters che permettono di ritornare informazioni di sintesi sullo stato attuale delle checklist;
  • mutations: sono le operazioni che mutano lo stato. Nel caso specifico abbiamo un'unica mutation toggle che permette di modificare lo stato di un singolo item;
  • actions: sono le azioni che vengono "dispatchate" sullo store, l'interfaccia pubblica esterna delle mutations.

Inizializzazione dell'app e template base

L'inizializzazione dell'app avviene nel file src/app.js. In questo file vengono incluse tutte le dipendenze necessarie, vengono registrati i 4 componenti globali e viene inizializzato l'oggetto Vue al quale viene passato, oltre all'opzione el, anche lo store descritto in precedenza.

import Vue from 'vue';
import store from './vuex/store.js';
import 'bootstrap/dist/css/bootstrap.min.css';
import VuexChecklistApp from './components/VuexChecklistApp.vue';
import VuexChecklistWrapper from './components/VuexChecklistWrapper.vue';
import VuexChecklistSummary from './components/VuexChecklistSummary.vue';
import VuexChecklist from './components/VuexChecklist.vue';
Vue.component('vuex-checklist-app', VuexChecklistApp);
Vue.component('vuex-checklist-wrapper', VuexChecklistWrapper);
Vue.component('vuex-checklist-summary', VuexChecklistSummary);
Vue.component('vuex-checklist', VuexChecklist);
new Vue({
    el: '#app',
    store: store
});

Il template iniziale (index.html) non presenta nulla di particolarmente interessante:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Vuex checklist workshop</title>
    </head>
    <body>
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <a class="navbar-brand" >Vuex checklist workshop</a>
        </nav>
        <div id="app" style="margin-top: 20px">
            <vuex-checklist-app />
        </div>
        <script src="app.js"></script>
    </body>
</html>

Il componente vuex-checklist-app

Il componente Vue principale è vuex-checklist-app, inserito nel file src/components/VuexChecklistApp.js. Esso viene istanziato direttamente dal template globale dell'applicazione e ha come unico scopo quello di definire il layout base e di istanziare due componenti più specifici: vuex-checklist-wrapper e vuex-checklist-summary.

<template>
    <div class="container">
        <div class="row">
            <div class="col-md-10">
                <vuex-checklist-wrapper />
            </div>
            <div class="col-md-2">
                <vuex-checklist-summary />
            </div>
        </div>
    </div>
</template>

export default {
    name: 'vuex-checklist-app'
}

Il componente vuex-checklist-summary

Questo componente, incluso nella spalla destra del sito e definito nel file src/components/VuexChecklistSummary.js, permette di avere un riassunto della situazione attuale delle checklist. Esso sfrutta i getters definiti all'interno dello store e già discussi in precedenza.

<template>
    <div>
        <p>
            Checklists: <strong>{{ checklistCount }}</strong>
        </p>
        <p>
            Done checklists: <strong>{{ completedChecklistCount }}</strong>
        </p>
        <p>
            Items: <strong>{{ itemsCount }}</strong>
        </p>
        <p>
            Done items: <strong>{{ completedItemsCount }}</strong>
        </p>
    </div>
</template>

import { mapGetters } from 'vuex'
export default {
    name: 'vuex-checklist-summary',
    computed: mapGetters([ 'checklistCount', 'completedChecklistCount', 'itemsCount', 'completedItemsCount' ])
}

Nel codice Javascript viene inclusa la funzione mapGetters dal modulo vuex. Questa funzione permette di accedere con semplicità ai metodi getters dello store e di linkarli come proprietà computed del componente Vue. In questo modo possiamo utilizzare le property dello store passate come parametro, esattamente come se fossero proprie del componente. Nel template HTML è possibile vedere il loro agnostico utilizzo.

Il componente vuex-checklist-wrapper

Il wrapper rappresenta la parte principale del layout e permette di istanziare le varie checklist. Esso è definito nel file src/components/VueChecklistWrapper.vue.

<template>
    <div class="row">
        <div v-for="checklist in checklists" class="col-md-4">
            <vuex-checklist :checklist="checklist" />
        </div>
    </div>
</template>

import { mapState } from 'vuex'
export default {
    name: 'vuex-checklist-wrapper',
    computed: mapState([ 'checklists' ])
}

Anche in questo componente usiamo una funzione del modulo vuex mapState. Anch'essa offre una scorciatoia per accedere ai dati presenti nello store, ma in questo caso permette di utilizzare i dati grezzi direttamente dallo store senza dei particolari getters. Grazie ad essa possiamo accedere all'elenco delle checklist come fossero dati propri del componente Vue.

Il componente vuex-checklist

L'ultimo componente rappresenta la singola checklist, istanziato all'interno di un ciclo dal wrapper. Esso è definito nel file src/components/VueChecklist.vue.

<template>
    <div class="card">
        <img class="card-img-top" :src="image">
        <div class="card-body">
            <h5 class="card-title">{{ checklist.name }}</h5>
            <p class="card-text">{{ checklist.description }}</p>
            <ul class="list-group list-group-flush">
                <li v-for="item in checklist.items" class="list-group-item" :class="{ 'list-group-item-success' : item.done }" @click="click(item)">{{ item.name }}</li>
            </ul>
        </div>
    </div>
</template>

import store from '../vuex/store.js';
export default {
    name: 'vuex-checklist',
    props: {
        checklist: {
            required: true
        }
    },
    computed: {
        image: function() {
            return "https://placeimg.com/200/70/tech?" + Math.random();
        }
    },
    methods: {
        click(item) {
            store.dispatch({
                type: 'toggle',
                checklistId: this.checklist.id,
                itemId: item.id
            });
        }
    }
}

La parte principale è sicuramente quella dedicata all'evento click, invocato appunto da un click dell'utente sul singolo elemento. Esso scatena un'azione di tipo toggle sullo store passando come parametri l'id della checklist e dell'elemento selezionato. L'invocazione dell'azione, come visto prima, scatenerà una mutation sullo store che altererà lo stato e che, grazie alle varie computed property, verrà renderizzata in tempo reale all'utente

Conclusioni

Per quanto l'applicazione sia abbastanza semplice e poco funzionale, essa ci permette di approfondire praticamente tutti gli aspetti di una applicazione Vuex, introducendo non solamente actions e mutations, ma anche dei getters custom. Grazie all'accesso globale delle informazioni importanti, i componenti risultano più snelli in quanto non c'è la necessità di un continuo passaggio di informazioni.

Ovviamente un'implementazione che non prevede l'integrazione con un server e relativo database, ha poco senso e non offre appunto persistenza. La mancanza di questo aspetto è da intendersi esclusivamente a scopo didattico e per potersi concentrare sugli aspetti frontend.

Ti consigliamo anche