Introduction à

 
logo
 
 
Par Sébastien Rodriguez
@sebtiz13

Who I am

 
  • Développeur chez Laëtis
  • Développeur front/backend PHP et JS
  • Éternel perfectionniste et optimiseur

Bref historique 

 
Créer par Evan YOU ancien développeur chez Google
 
  • Février 2014 : Vue.js est inspiré de la partie vue de AngularJS
  • Octobre 2015 : Version 1.0 réécriture de l'API
  • Décembre 2015 : Vue-cli
  • Septembre 2016 : Version 2.0 Virtual-DOM et Server-Side Rendering

Qu'est-que c'est ?

 
  • Axer sur l'interface
  • Composants
  • Rechargement à chaud
  • Débogage temporel
  • Apprentissage incrémentale

Qui l'utilise ?

Un exemple ?

Un peu de lexique

  • Directives

  • Models

  • Computed (propriétés calculées)

  • Watchers (observateurs)

  • State (état)

  • Modifiers (modificateurs)

Comment ça marche ?

Avant de débuter

Révisions ES6

Déclaration de variables

function fn() {
  let foo = "bar"
  var foo2 = "bar"
  if (true) {
    let foo // pas d'erreur, foo === undefined
    var foo2 // Les déclarations "var" ne sont pas scopées au niveau bloc foo2 est en réalité écrasée !
    foo = "qux"
    foo2 = "qux"
    console.log(foo) // "qux", la variable appartient au scope de son blocs (le "if")
    console.log(foo2) // "qux"
  }
  console.log(foo) // "bar", la variable appartient au scope de son bloc (la fonction "fn")
  console.log(foo2) // "qux"
}

let is a new var

Fonctions fléchées

let double = number => number * 2 // Le return est implicite
double(5) // 10

this.divClicked = false
document.querySelector('div').addEventListener('click', event => {
    event.preventDefault()
    this.divClicked = true
    // Some code
})

(arrow function)

var double = function (number) {
    return number * 2
}
double(5) // 10

this.divClicked = false
document.querySelector('div').addEventListener('click', function (event) {
    event.preventDefault()
    this.divClicked = true
    // Some code
}.bind(this))

find / findIndex

let ages = [12, 19, 6, 4]

let firstAdult = ages.find(age => age >= 18) // 19
let firstAdultIndex = ages.findIndex(age => age >= 18) // 1

Opérateur de diffusion

let numbers = [9, 4, 7, 1]
Math.min(...numbers) // 1
// Math.min(9, 4, 7, 1)

// Convertis NodeList en Array
let divsArray = [...document.querySelectorAll('div')]

// Fusion d'objet
let object = { tata: 'tata', toto: 'toto' }
let newObject = { ...object, titi: 'titi' }
// newObject : { tata: 'tata', toto: 'toto', titi: 'titi' }

(spread operator)

Décomposition des objets

let user = {
    firstname: 'Michel',
    lastname: 'Dupont',
    nickname: 'titi'
}

// pseudo est changé en username et on récupère firstname
function sayFirstName ({ nickname: username, firstname }) {
    console.log(lastname) // undefined
    return 'Le prénom de ' + username + ' est ' + firstname
}

sayFirstName(user) // Le prénom de titi est Michel

Syntaxe raccourcie

// Raccourcis pour les noms de propriétés
var a = "toto", b = 42, c = {}
var o = { a, b, c }

// Raccourcis pour les noms de méthodes
var o = {
  mafonction: function () {}
  mafonction () {}
}

// Noms calculés pour les propriétés
var prop = "toto"
var o = {
  [prop]: "hey",
  ["tr" + "uc"]: "ho",
}

C'est parti pour les bases !

L'instance

...
<div id="app">
    <!-- Vue.JS -->
</div>
...
<script>
    let app = new Vue({
      el: '#app'
    })
</script>

Les variables

...
<div id="app">
  {{ message }}
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue !'
      }
    })
</script>

Les directives

...
<div id="app">
  <img v-bind:src="image" />
  <img :src="image" /> <!-- syntaxe courte -->
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        image: 'https://wallpapers.wallhaven.cc/wallpapers/full/wallhaven-580335.jpg'
      }
    })
</script>

Les conditions

...
<div id="app">
  <div v-if="!image">
    Oh noooon !
  </div>
  <img v-else src="https://wallpapers.wallhaven.cc/wallpapers/full/wallhaven-509703.png"/>

  <img v-show="isRed" src="https://wallpapers.wallhaven.cc/wallpapers/full/wallhaven-377340.jpg"/>
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        image: true
        isRed: false
      }
    })
</script>

Les boucles

...
<div id="app">
  <ul>
    <li v-for="name in users">{{ name }}</li>
  </ul>
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        users: [ 'toto', 'titi', 'tata' ]
      }
    })
</script>

Les évènements

...
<div id="app">
  <button v-on:click="alertYou" >Click me !</button>
  <button @click="alertYou" >Click me !</button> <!-- syntaxe courte -->
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      methods: {
        alertYou () {
            alert('Hey you')
        }
      }
    })
</script>

Modificateurs d'évènements

<div id="app">
    <!-- La propagation de l'évènement `click` sera stoppée -->
    <a @click.stop></a>

    <!-- L'évènement `submit` ne rechargera plus la page -->
    <form @submit.prevent></form>

    <!-- L'évènement ne s'applique qu'à l'élément pas aux enfants -->
    <div @click.self="doThat">...</div>

    <!-- Appel submit() quand on appuis sur la touche entrée -->
    <input @keyup.enter="submit">

    <!-- Utilise le mode "capture" de addEventListener -->
    <!-- (l'évènement est lancé avant celui des enfants) -->
    <div @click.capture="alert('Capture')">
        <button @click="alert('Hello world')">Click me</button>
    </div>

    <!-- 2.1.4+ : L'évènement n'est lancé qu'une fois -->
    <div @click.once="sayHello">...</div>
</div>

Les modèles

...
<div id="app">
  <input v-model="message">
  {{ message }}
</div>
...
<script>
    let app = new Vue({
      el: '#app'
      data: {
        message: 'I am watching you'
      }
    })
</script>

Modificateurs de modèles

<div id="app">
    <!-- synchronisé après le "change" au lieu du "input" -->
    <input v-model.lazy="message">

    <!-- Force le type number -->
    <input v-model.number="age" type="number">

    <!-- Supprime les espaces superflus -->
    <input v-model.trim="message">
</div>

Les transitions

...
<div id="app">
    <button @click="show = !show"> Permuter </button>
    <transition name="fade">
        <p v-if="show">Hello world</p>
    </transition>
</div>
...
<script>
    let app = new Vue({
      el: '#app'
      data: {
        show: true
      }
    })
</script>
<style>
    .fade-enter-active, .fade-leave-active {
        transition: opacity .5s;
    }
    .fade-enter, .fade-leave-to {
        opacity: 0;
    }
</style>

Observateurs
&
Propriétés calculées

Les observateurs

...
<div id="app">
  {{ fullName }}
</div>
...
<script>
    let app = new Vue({
      el: '#app'
      data: {
        firstName: 'Toto',
        lastName: 'Tata',
        fullName: 'Toto Tata'
      },
      watch: {
        firstName (val) {
            this.fullName = val + ' ' + this.lastName
        },
        lastName (val) {
            this.fullName = this.firstName + ' ' + val 
        }
      }
    })
</script>

Propriétés calculées

...
<div id="app">
  {{ fullName }}
</div>
...
<script>
    let app = new Vue({
      el: '#app'
      data: {
        firstName: 'Toto',
        lastName: 'Tata'
      },
      computed: {
        fullName () {
            return this.firstName + ' ' + this.lastName
        }
      }
    })
</script>

Gestion des formulaires

Input

...
<div id="app">
    <input v-model="messsage" placeholder="Saisir un message">
    <span>Message: {{ messsage }}</span>
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        messsage: ''
      }
    })
</script>

Checkbox

...
<div id="app">
    <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
    <label for="jack">Jack</label>
    <input type="checkbox" id="john" value="John" v-model="checkedNames">
    <label for="john">John</label>
    <span>Noms cochés: {{ checkedNames }}</span>
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        checkedNames: []
      }
    })
</script>

Radio

...
<div id="app">
    <input type="radio" id="jack" name="name" value="Jack" v-model="checkedNames">
    <label for="jack">Jack</label>
    <input type="radio" id="john" name="name" value="John" v-model="checkedNames">
    <label for="john">John</label>
    <span>Noms choisi: {{ checkedName }}</span>
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        checkedName: ''
      }
    })
</script>

Liste déroulante

...
<div id="app">
    <select v-model="selected">
        <option value="123">123</option>
        <option value="256">256</option>
    </select>
    <span>Montant {{ selected }}</span>
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        selected: 0
      }
    })
</script>

Passer une valeur litérale

...
<div id="app">
    <label for="check">Check me :</label>
    <input type="checkbox" value="{ text: 'Hello world' }" v-model="message" id="check">
    {{ message }} <!--  [ "{ text: 'Hello world' }" ] -->
</div>
...
<script>
    let app = new Vue({
      el: '#app',
      data: {
        message: []
      }
    })
</script>

Les composants

La création

...
<div id="app">
    <my-component></my-component>
</div>
...
<script>
    let myComponent = Vue.component('my-component', {
        template: '<div>Un composant personnalisé !</div>'
    })
    let app = new Vue({
      el: '#app',
      components: {
        myComponent
      }
    })
</script>

Limitation avec le DOM

<!-- Erreur ! -->
<table>
    <my-row>...</my-row>
</table>

<!-- Solution ! -->
<table>
    <tr is="my-row"></tr>
</table>

<ul>, <ol>, <table>, <select>, <option>

Data doit être une fonction

...
<div id="app">
    <my-component></my-component>
    <my-component></my-component>
    <my-component></my-component>
</div>
...
<script>
    Vue.component('my-component', {
      template: '<span>{{ message }}</span>',
      data: {
        message: 'bonjour' // Warning : Même objet !
      }
    })
    ...
</script>

La Composition

descente de props, remontée d’évènements.

Les propriétés

...
<div id="app">
    <my-component message="bonjour !"></my-component>
</div>
...
<script>
    Vue.component('my-component', {
        props: ['message'],
        template: '<span>{{ message }}</span>'
    })
    ...
</script>

Littérales vs dynamiques

<div id="app">
    <!-- Ceci est un String "1" -->
    <comp some-prop="1"></comp>
    <!-- Ceci est un Number 1 -->
    <comp :some-prop="1"></comp>
</div>

Validation des propriétés

Vue.component('child', {
    props: {
        propA: String,
        propB: [String, Number],
        propC: { type: String, required: true }
        propD: { type: String, default: 'Hello world' }
        propE: { type: Object, default () { return { message: 'hello' } } }
        propF: {
            validor (value) {
                return (value / 10) > 10
            }
        }
    }
})

Les slots

<!-- Utilisation -->
<div>
    <h1>Je suis le titre du parent</h1>
    <my-component>
        <p>Ceci est le contenu original</p>
    </my-component>
</div>

<!-- Composant -->
<div>
    <h2>Je suis le titre de l'enfant</h2>
    <slot> Serra remplacé </slot>
</div>

<!-- Résultat -->
<div>
    <h1>Je suis le titre du parent</h1>
    <div>
        <h2>Je suis le titre de l'enfant</h2>
        <p>Ceci est le contenu original</p>
    </div>
</div>

Les slots nommés

<!-- Utilisation -->
<app-layout>
  <h1 slot="header">Voici un titre de page</h1>

  <p>Un paragraphe pour le contenu principal.</p>
  <p>Et un autre.</p>

  <p slot="footer">Ici plusieurs informations de contact</p>
</app-layout>

<!-- Composant -->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

Les évènements personnalisés 

...
<div id="app">
    <child @hello="alert('Enfant dit bonjour')"></child>
</div>
...
<script>
    Vue.component('child', {
        props: ['message'],
        template: '<button @click="sayHello">Dire bonjour</button>'
        methods: {
            sayHello () {
                this.$emit('hello')
            }
        }
    })
    ...
</script>

Aller plus loin

Composants monofichiers

<template>
    <p>Hello {{ name }}</p>
    <button @click="sayHello">Dire bonjour</button>
</template>
<script>
export default {
    data () {
        return {
            name: 'Jon Snow'
        }
    }
    methods: {
        sayHello () {
            alert(`You don't know`)
        }
    }
}
</script>
<style scoped>
p {
    font-size: 2rem;
}
</style>

Routeur

...
<div>
    <nav>
        <!-- `<router-link>` sera rendu en tag `<a>` par défaut -->
        <router-link to="/foo">Aller à Foo</router-link>
        <router-link to="/bar">Aller à Bar</router-link>
    </nav>
    <!-- La page sera rendu ici -->
    <router-view></router-view>
</div>
...
<script>
const router = new VueRouter({
    routes: [
        { path: '/foo', component: { template: '<div>foo</div>' } },
        { path: '/bar', component: { template: '<div>bar</div>' } }
    ]
})

const app = new Vue({
  router
}).$mount('#app')
</script>

(vue-router)

Validation de formulaire

import { required, minLength } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      name: ''
    }
  },
  validations: {
    name: {
      required, // Name est requis
      minLength: minLength(4) // Minimum 4 lettres
    }
  }
}

(vuelidate)

Internationalisation

(vue-i18n)

...
<div id="app">
    <p>{{ $t("message") }}</p> <!-- hello world -->
</div>
...
<script>
const messages = {
  en: {
    message: 'hello world'
  },
  fr: {
    message: 'Bonjour tout le monde'
  }
}

const i18n = new VueI18n({
  locale: 'en', // Définit la langue
  messages, // Initialise le dictionnaire
})

const app = new Vue({ i18n }).$mount('#app')
</script>

Gestion d'état centralisé

(vuex)

Rendu côté serveur

(Nuxt.js)

Conclusion

Les avantages

  • Code très lisible avec les .vue
  • Code maintenable
  • Scalabilité
  • Apprentissage incrémentale

La popularité grandissante

Merci !

Made with Slides.com