fullstack/vuejs
Auteur : J.C. Buisson
partie 1 - partie 2 - partie 3
VueJS - présentation générale
Vue ne gère… que la vue
les templates sont généralement à base html, mais pas nécessairement (ex: nativescript-vue)
les autres fonctions (routage, requêtes http, stockage centralisé de l’état, etc.) sont gérées dans des modules séparés, facilement pluggables
Premier exemple
index.html
<html>
<div id="app">
<p>{{ message }}</p>
<p>This message has a length of {{ message.length }} characters
</div>
</html>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})
</script>
l’exécution du script
new Vue({...
rend dynamique le template<div id="app">...</div>
la section
data
recense les données associées à ce bloc contrôlé par Vue, elles constituent l’état du composantles expressions contenues dans les templates à moustache des parties
text
d’éléments html sont évaluées, et remplacées par leurs valeurs.
L’expression peut être n’importe quelle expression javascript contenant des variables issues (entre-autres) dedata
Deuxième exemple : double binding
index.html
<html>
<div id="app">
<h1> {{ name }} </h1>
<input v-model="name" placeholder="Enter your name" />
<p>Hello, {{ name }}</p>
</div>
</html>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
name: ''
}
})
</script>
L’attribut v-model="name"
indique que this.name
est le modèle
de la vue que constitue l’input.
Cela signifie que, à chaque fois que this.name
change, le contenu visible de l’input change, ainsi que le contenu visible des templates à moustache qui contiennent name
(binding simple).
Inversement, chaque fois que l’utilisateur modifie le contenu de l’input en y éditant le texte, la valeur de this.name
et des templates à moustache changent également de sorte que toutes les vues sont synchronisées sur le modèle this.name
(double-binding).
En réalité, <input v-model="something">
est du sucre syntaxique pour :
<input
v-bind:value="something"
v-on:input="something = $event.target.value"
/>
data
doit être une fonction
Si le champ data
de la définition d’un composant est un simple objet, ses propriétés seront partagées par toutes les instances de ce composant (pourquoi ?), ce qui n’est pas souhaitable. data
doit être une fonction qui renvoie un objet contenant les propriétés à accéder, par exemple :
Un attribut attr
de data
est accessible en lecture / écriture en tant que this.attr
Propriétés calculées (computed properties)
Exemple :
- elles sont accessibles comme des propriétés ordinaires du composant
this
- elles apportent beaucoup de déclarativité aux composants Vue.
- les propriétés calculées sont mises en cache selon leurs dépendances
Méthodes
À noter: L’écriture d’une méthode de composant
methods
ne doit pas utiliser les fat arrows carthis
ne sera alors par le composant lui-même, mais l’objetwindow
Événements
v-on:click="addItem"
Boucles et conditionnelles
https://codesandbox.io/s/jlo7p90lo5
index.html
<html>
<div id="app">
<div v-if="foods.length > 0">
<ul>
<li v-for="food in foods">
{{ food.name }} {{ food.qty }}
</li>
</ul>
</div>
</div>
</html>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
foods: [
{ name: "Tomate", qty: 10 },
{ name: "Boeuf", qty: 30 }
]
}
})
</script>
Activité : édition d’une facture
Réaliser un formulaire de saisie d’items pour une facture selon le modèle suivant :
Composants et sous-composants
Instanciation d’un composant
- un composant est une
Vue
paramétrable avec des propriétésprops
fournies par son composant père - une prop
myprop
d’un composant est accessible en lecture parthis.myprop
; une tentative d’écriture provoque une erreur
Exemple
index.html
<html>
<div id="counter">
<p>{{ total }}</p>
<button-counter @increment="incrementTotal" text="CLICK"></button-counter>
<button-counter @increment="incrementTotal" text="CLICK2"></button-counter>
</div>
</html>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('button-counter', {
template: '<button @click="incrementCounter">{{ text }}</button>',
props: ['text'],
methods: {
incrementCounter: function () {
this.$emit('increment')
}
},
})
new Vue({
el: '#counter',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
</script>
Modèle de communication entre composants
- pour faire court : props down, events up
- l’héritage des
props
ne va que du père vers le fils, il ne descend pas vers les petit-fils - un composant émet des événements que seul son père peut écouter (et pas son grand-père). Pour émettre un événement
'myevent'
avec les paramètresparam1
, …, il faut appelerthis.$emit('myevent', param1, ...)
Le modèle de communication est donc simple et bien cloisonné, strictement entre un père et son fils. Les propriétés descendent du père vers le fils, alors que des événements remontent du fils vers le père.
Single-file components
On peut décrire tous les éléments d’un composant (template, script, style) dans un même fichier d’extension .vue
. Avec Webpack et son plugin vue-loader
ces parties sont extraites et traitées séparément. Cela permet :
- une édition simplifiée avec coloration syntaxique si on installe dans son IDE un plugin pour fichiers
.vue
- la transpilation de javascript ES6 ou ES7 avec Babel
- des modules javascript
CommonJS
- un styling CSS dont le scope peut être restreint au composant
- le hot-reload
La mise en place d’un tel système se fait simplement en utilisation vue-cli
npm install -g @vue/cli
vue create my-project
cd my-project
npm run serve
Activité: créer un éditeur Markdown
Utiliser le package marked
Noms de props
et des événements dans les templates : kebab-case
Les noms des attributs en HTML sont case-insensitive. C’est la raison pour laquelle les noms de props
et d’événements doivent être utilisés en forme kebab-case
dans les templates .vue
. Par exemple, si un composant a une section props: ['myProp
],
myPropdeviendra
my-propdans l'instanciation du composant dans un template
.vue:
Activité
Refaire l’application d’édition de facture en utilisant au maximum une structure en composants, et en utilisant un fichier .vue
par composant
Cycle de vie d’un composant Vue
On peut créer des hooks
pour chacun de ces moments du cycle de vie, par exemple :
Exemple de toolkit de composants visuels : Vuetify
https://codesandbox.io/s/2pnvq5n3ln
index.js
import Vue from "vue"
import Vuetify from "vuetify"
import App from "@/App"
Vue.config.productionTip = false
Vue.use(Vuetify)
/* eslint-disable no-new */
new Vue({
el: "#app",
template: "<App/>",
components: { App }
})
index.html
App.vue
<template>
<v-form v-model="valid">
<v-text-field
label="Identifiant"
v-model="name"
:rules="nameRules"
:counter="10"
required
></v-text-field>
<v-text-field
label="E-mail"
v-model="email"
:rules="emailRules"
required
></v-text-field>
</v-form>
</template>
<script>
export default {
data () {
return {
valid: false,
name: '',
nameRules: [
(v) => !!v || "L'identifiant est obligatoire",
(v) => v.length > 3 || "L'identifiant doit faire plus de 3 caractères"
],
email: '',
emailRules: [
(v) => !!v || "L'e-mail est obligatoire",
(v) => /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(v) || "L'e-mail doit être valide"
]
}
}
}
</script>
<style>
html, body {
background-color: #F0F0F0;
font-family: sans-serif;
font-size: 100%;
height: 100%;
width: 100%;
margin: 10px;
padding: 0px;
overflow-y: auto
}
</style>
Activité : facture avec Vuetify
Améliorer le visuel de la facture avec Vuetify ou tout autre toolkit de composants graphiques
Installation vue + vuetify + webpack avec vue-cli
vue create my-project
cd my-project
vue add vuetify