Vue.js

Vue.js

In questo articolo parleremo di Vue.js (si pronuncia come la parola view), una libreria per la realizzazione applicazioni web di front end, che dalla versione 2 (uscita circa un anno fa ma che non aveva stravolto la precedente) sta sempre più acquisendo popolarità, come segnalano anche le ultime statistiche da Github.
Vue.js non porta il “peso” di una grossa azienda alle sue spalle come per Angular (Google) e React (Facebook) ma è idea e lavoro (si è formato e si sta formando un team dietro) di Evan You, che forte della sua esperienza in Google, grazie alla quale si era imbattuto con Angular, ha creato una libreria analizzando i competitor del momento e puntando principalmente su versatilità, performance e semplicità/accessibilità.
Come già fatto per l’articolo introduttivo su React implementeremo l’ormai classico esempio todo-list in modo da addentrarsi su alcuni concetti di questa libreria.

Caratteristiche

Per chi ha già dato un’occhiata alla guida ufficiale (davvero ben fatta e consiglio caldamente) avrà sicuramente notato che ci sono aspetti già visti nel mondo Angular e in quello React. Anche qui abbiamo concetti di direttive, filtri, two-way binding (ottimizzato a confronto di Angular), virtual DOM, componenti, reattività, isomorfico ma il tutto, almeno lato sviluppatore, gestito in maniera semplice che porta certamente ad una curva di apprendimento più dolce.

Non è compito di questo post spiegare nel dettaglio tutte queste caratteristiche che come detto sono ben trattate dalla documentazione ufficiale e dai precedenti articoli ma alcune di esse saranno toccate con mano nell’esempio.

Setup del progetto

Vediamo subito la index della nostra applicazione:

<!DOCTYPE html>
<html>
<head>
  <title>Vue TodoList</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.0/css/bulma.min.css" />
</head>
<body>

  <div id="app">
  </div>

  <script src="js/todo-app.js"></script>
  <script src="js/todo-form.js"></script>
  <script src="js/todo-list.js"></script>

  <script>
    new Vue({
      el: '#app',
      template: '<todo-app></todo-app>'
    })
  </script>
</body>
</html>

Per essere subito pronti a giocare con Vue, l’unica dipendenza è la libreria ufficiale dal peso molto ridotto visto che siamo sui 20kb.

Componenti

Come si può notare l’entry point dell’applicazione è rappresentata dalla riga di codice:

new Vue({
    el: '#app',
    template: '<todo-app></todo-app>'
})

nella quale viene creata un istanza di Vue e si dichiara il componente <todo-app> che sarà aggiunto al div wrapper con id app visto nella index.

Vue.component('todo-app', {
    template: `
        <section class="section"> 
            <div class="content"> 
                <h1 class="has-text-centered"> Todo App </h1> 
            </div> 
            <todo-form @add="addTodo"></todo-form> 
            <hr /> 
            <todo-list :todos="todos" @done="deleteTodo"></todo-list> 
        </section>
    `,
    data: function() {
        return {
            todos: []
        }
    },
    methods: {
        addTodo: function(todo) {
            this.todos.push(todo)
        },
        deleteTodo: function(index) {
            this.todos.splice(index, 1)
        }
    }
})

Il componente todo-app contiene lo stato globale dell’applicazione ovvero la lista dei Todo e ha il compito di ascoltare gli eventi emessi dagli altri componenti per l’aggiornamento dello stato stesso ovvero l’aggiunta e la cancellazione. Come si può notare, logica e template stanno assieme ma non è l’unico modo per dichiarare un componente; in questo progetto di esempio i template dei componenti sono descritti tramite sintassi template literal caratteristica della specifica ES6 ma Vue garantisce il supporto su tutti i browser compatibili con la specifica ES5.

Come appena detto, una delle caratteristiche di Vue è la possibilità di definire il template di un componente non in una sola modalità ma in più varianti ognuna con i suoi pro e contro:

string

Vue.component('custom-component', {
    data() { 
      return { 
        message: 'Hello World!' 
      } 
    },
    template: '<div> {{ message }} </div>'
})

semplice stringa di testo contenente il proprio markup.

template literal

Vue.component('custom-component', {
    data() { 
      return { 
        message: 'Hello World!' 
      } 
    },
    template: `
        <div>
          {{ message }}
        </div>
    `,
})

come sopra ma sfruttando il concetto di template literal di ES6.

x-template

Vue.component('custom-component', {
    data() { 
      return { 
        message: 'Hello World!' 
      } 
    },
    template: '#custom-component-template'
})
<script type="text/x-template" id="custom-component-template">
  <div>
    {{ message }}
  </div>
</script>

disaccoppiamento tra logica e template in file differenti, il primo in un file .js e l’altro nel file .html principale.

render

Vue.component('custom-component', {
  data() { 
    return { 
      message: 'Hello World!' 
    } 
  }
  render(h) {
    return h('div', this.message) 
  }
})

intera definizione del template mediante codice Javascript.

JSX 

Vue.component('custom-component', {
  data() {
    return {
      message: 'Hello World!'
    }
  }
  render(h) {
    return (<div> { this.message } </div>)
  }
})

sintassi cara a chi proviene dal mondo React.

Single File Component

<template>
   <div> {{ messsage }} </div>
</template

<script>
export default {
  data() {
    return {
      message: 'Hello World!'
    }
  }
}
</script>

la più utilizzata e si compone di un file con estensione .vue (richiede un tool di transpiling) nel quale si dichiara il proprio markup all’interno del tag template, la logica all’interno del tag script e opzionalmente il proprio stile all’interno del tag style.

Tornando al nostro componente noterete la sintassi :[nome_attributo] che è una scorciatoia di v-bind:[nome_attributo] che serve a fare il binding tra un attributo di un tag o una prop di un componente figlio con una variabile dello stato del componente; discorso analogo per @[nome_evento] che è una una scorciatoia di v-on:[nome_evento] atto a fare il binding tra un evento e un’azione (metodo) del componente.

Ogni cambiamento di stato provocherà un aggiornamento nel ciclo render delle parti interessate dell’applicazione in maniera automatica e trasparente.

Del concetto di props e nello specifico di :todos ne parleremo più avanti.

Vue.component('todo-form', {
    template: `
        <form @submit.prevent="add">
            <div class="field is-grouped">
                <p class="control is-expanded">
                    <input  type="text"
                            class="input"
                            v-model="text"
                            placeholder="Add todo..."
                            required />
                </p>
                <p class="control">
                    <button type="submit" class="button is-primary"> Add </button>
                </p>
            </div>
        </form>
    `,
    data: function() {
        return {
            text: ''
        }
    },
    methods: {
        add: function() {
            this.$emit('add', this.text)
            this.text = ''
        }
    }
})

Il componente todo-form ha il compito di mostrare il form per l’inserimento del Todo e notificarlo al componente padre in ascolto mediante l’evento add.

Qui possiamo notare come viene implementato il two-way binding tra il componente input e una variabile dello stato del componente; mediante la direttiva v-model tutti i cambiamenti dal componente input vengono sincronizzati con la variabile in tempo reale e viceversa.

Nota su questa importante direttiva:

<input type="text" :value="text" @input="text = $event.target.value" />

questa è la forma equivalente di ciò che effettivamente accade ovvero l’aggiornamento avviene tramite l’ascolto dell’evento input che va ad aggiornare la variabile che a sua volta è associata all’attributo value dell’input.

Detto questo, è possibile avere un binding ritardato mediante direttiva v-model.lazy (.lazy è un esempio di quello che viene chiamato modifier, ve ne sono già  definiti alcuni nativamente  e possono essere applicati a direttive, props e eventi) che, per analogia come detto sopra, vede nella sua forma equivalente:

<input type="text" :value="text" @change="text = $event.target.value" />

Faccio questa premessa perché i propri componenti custom (immaginate di dover fare un componente che fa da wrapper a un input o di realizzare componente di input non nativo) possono utilizzare la direttiva v-model ma eventuali modifier non possono essere applicati e quindi è bene tenere a mente la forma equivalente per realizzare un binding lazy. Nota: $event.target.value si riferisce al fatto che il binding è stato fatto su eventi input/change nativi ma per un proprio componente custom l’emissione di questi eventi è a carico dello sviluppatore e quindi potrà passare qualsiasi valore (coerentemente con il type del proprio v-model).

Vue.component('todo-list', {
    template: `
        <table class="table is-fullwidth">
            <tbody>
                <tr v-for="todo in todos">
                    <td> {{ todo }} </td>
                    <td>
                        <a  href="#" 
                            class="button is-pulled-right" 
                            @click.prevent="done(index)"> Done </a>
                    </td>
                </tr>
            </tbody>
        </table>
    `,
    props: {
        todos: {
            type: Array,
            required: true
        }
    },
    methods: {
        done: function(index) {
            this.$emit('done', index)
        }
    }
})

Il componente todo-list ha il compito di mostrare l’elenco dei Todo e notificare al componente padre quando viene finalizzato mediante evento done. Per realizzare tutto ciò, viene utilizzato il concetto di props ovvero il modo di passare un dato da un componente padre a un componente figlio; una singola prop oltre che dal nome può essere descritta dal vincolo di obbligatorietà (required), da un valore di default e da una funzione di validazione.

E’ importante sapere che di norma non è possibile modificare il valore di una prop dal componente figlio ma è tuttavia in alcuni casi utile che il componente padre possa ricevere in tempo reale eventuali aggiornamenti su una prop passata al figlio e nello specifico è possibile nel seguente modo:

this.$emit('update:nomeProp', valoreProp)

il figlio emette un evento che inizia per update:

<child :nomeProp.sync="var"></child>

e il padre riceve gli aggiornamenti nella variabile associata alla prop mediante il modifier sync.

Da notare anche @click.prevent (discorso analogo anche per il componente todo-form per l’evento @submit.prevent) che associa il metodo done ma prima di eseguirlo, in modo trasparente, effettua la funzione prevent dell’evento stesso; la sua forma equivalente e comunque utilizzabile sarebbe:

<a  href="#" class="button is-pulled-right" @click="$event.preventDefault(); done(index)"> Done </a>

 

Ovviamente i componenti sono una caratteristica, in generale e di Vue, molto importante ma per onor di cronaca tutto l’esempio poteva essere realizzato all’interno della index con stato e azioni gestiti dall’istanza Vue e il template HTML dei vari componenti all’interno del div wrapper.

Vue.js Todo App

Conclusioni

Questo post ha lo scopo di dimostrare quanto possa essere intuitivo e immediato iniziare a lavorare con Vue senza considerare che non sono stati utilizzati e menzionati concetti, quali computed property, watch,  ecc, che possono risultare importanti nella costruzione di applicazioni con complessità maggiore.

Come per altri competitor, come React, si parla di libreria e non di framework anche se a contorno sono nate altre librerie/plugin ufficiali e non, per una migliore gestione di un’intera applicazione quali vue-router (per il route di un’applicazione), vuex (gestione centralizzata dello stato) ecc.

A conferma di quanto detto in fase di introduzione, ovvero il sempre più interesse della community, vi segnala questo link che contiene un riepilogo di qualsiasi cosa che gira attorno a Vue.

Nel prossimo post introdurremo concetti più avanzati e utilizzeremo vue-cli per la creazione di un progetto e nello specifico configurato con template webpack.

Potete trovare il sorgente di questa applicazione su questo repository GitHub.

Walter Tommasi

Web e Mobile developer, da sempre legato al mondo Java ma con un occhio verso il mondo Javascript. Ad oggi ricopro la figura di Full Stack Developer. Questo il mio account GitHub