inspiré de la documentation officielle
https://interactly.glitch.me/
Framework JavaScript pour le développement d'application web
Simplifie le développement d'interface dynamique
Populaire : Adobe, GitLab, ...
Juil 2013 - Premier commit par Evan You (@Google)
Fév 2014 - Version 0.9
Oct 2015 - Version 1.0
Sep 2016 - Version 2.0
Sep 2020 - Version 3.0
Réelle plus value
Puissant
Léger
Open source
Populaire
Communauté active
Maintenu
Bien documenté
Permet de séparer la vue, le modèle de données et la logique applicative
Version de développement
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
Version de production
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello world !'
}
})
Hello world !
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello world !'
}
})
Hello world !
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello world !'
}
})
Vue is reactive !
> app.message = "Vue is reactive";
LIVE
<div id="app">
<img v-bind:src="url" />
</div>
var app = new Vue({
el: '#app',
data: {
url: "https:// [...] "
}
})
LIVE
<div id="app">
<p v-if="seen">Je suis visible</p>
</div>
var app = new Vue({
el: '#app',
data: {
seen: true
}
})
LIVE
<div id="app">
<ol>
<li v-for="verb in verbs">
{{ verb.text }}
</li>
</ol>
</div>
var app = new Vue({
el: '#app',
data: {
verbs: [
{ text: 'Veni' },
{ text: 'Vidi' },
{ text: 'Vici' }
]
}
})
LIVE
<div id="app">
<p>{{ message }}</p>
<button v-on:click="capitalize">
Capitaliser !
</button>
</div>
var app = new Vue({
el: "#app",
data: {
message: "Hello world !"
},
methods: {
capitalize() {
this.message = this.message.toUpperCase();
}
}
});
<div id="app">
<p>{{ message }}</p>
<input v-model="message" />
</div>
var app = new Vue({
el: "#app",
data: {
message: "Hello world !"
}
});
<div id="app">
<bold-italic></bold-italic>
</div>
Vue.component("bold-italic", {
template: "<b><i>Gratalic</i></b>"
});
var app = new Vue({
el: "#app",
data: {}
});
<div id="app">
<bold-italic v-bind:content="text">
</bold-italic>
</div>
Vue.component("bold-italic", {
template: "<b><i>{{ content }}</i></b>",
props: ["content"]
});
var app = new Vue({
el: "#app",
data: {
text: "Ninja !"
}
});
<div id="app">
<bold-italic
v-for="fruit in fruits"
v-bind:content="fruit.name"
v-bind:key="fruit.id"
></bold-italic>
</div>
Vue.component("bold-italic", {
template: "<b><i>{{ content }}</i></b>",
props: ["content"]
});
var app = new Vue({
el: "#app",
data: {
fruits: [
{ id: 0, name: "Apple" },
{ id: 1, name: "Pear" }
]
}
});
Créer une micro application permettant d'ajouter des éléments à une liste à l'aide d'un champ texte et d'un bouton.
Web IDE pré configuré + Aide : https://bit.ly/3kksZvz
Instance racine
└─ TodoList
├─ TodoItem
│ ├─ TodoButtonDelete
│ └─ TodoButtonEdit
└─ TodoListFooter
├─ TodosButtonClear
└─ TodoListStatistics
var vm = new Vue({
// options
})
// Notre objet de données
var data = { a: 1 }
// L'objet est ajouté à une instance de Vue
var vm = new Vue({
data: data
})
// Récupérer la propriété depuis l'instance
// retourne celle des données originales
vm.a == data.a // => true
// assigner la propriété à une instance
// affecte également la donnée originale
vm.a = 2
data.a // => 2
// ... et vice-versa
data.a = 3
vm.a // => 3
// Notre objet de données
var data = { a: 1 }
// L'objet est ajouté à une instance de Vue
var vm = new Vue({
data: data
})
// vm.a est réactive : ses mutations engendreront des mises à jour dans la vue #MVVM
vm.a
// Si on rajoute une propriété à l'état en cours de route, elle ne sera pas réactive : l'état local est ajouté au système de réactivité uniquement lors de l'instanciation de Vue
vm.b = 3
// Notre objet de données
var data = { a: 1 }
// L'objet est ajouté à une instance de Vue
var vm = new Vue({
el: '#example',
data: data
})
// L'instance de Vue expose de nombreuses méthodes et propriétés utiles
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
// $watch est une méthode de l'instance
vm.$watch('a', function (newVal, oldVal) {
// cette fonction de rappel sera appelée quand `vm.a` changera
})
new Vue({
data: {
a: 1
},
created() {
// `this` est une référence à l'instance de vm
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
Syntaxe basée sur HTML, validité requise
Les templates sont compilés pour être plus performants
Vue supporte JSX
<span>Message: {{ message }}</span>
<span v-once>
Ce message ne changera plus: {{ message }}
</span>
<div>Intepreté comme du texte : {{ rawHTML }}</div>
<div>
Intepreté comme du HTML :
<span v-html="rawHtml"></span> <!-- /!\ XSS -->
</div>
<div v-bind:id="dynamicId"></div>
<button v-bind:disabled="isButtonDisabled">Button</button>
// Cas des attributs booléan : si vaut null, undefined, ou false, l'attribut disabled n'est pas ajouté à l'élément
{{ number + 1 }}
{{ ok ? 'OUI' : 'NON' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
<!--Cas d'erreur :-->
<!--Ceci est une déclaration, pas une expression -->
{{ var a = 1 }}
<!--Le contrôle de flux ne marchera pas non plus, utilisez des expressions ternaires -->
{{ if (ok) { return message } }}
<!-- Cas d'erreur -->
{{ uneVariableGlobaleRandom }}
<!-- Succès -->
{{ Math.min(5, maVariableLocale) }}
v-for
v-if
v-else-if
v-else
v-*
<a v-bind:href="url"> ... </a>
<a v-on:click="doSomething"> ... </a>
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
Vue 2.6
Text
Donné au type string ou null, sinon erreur
<form v-on:submit.prevent="onSubmit"> ... </form>
<!-- syntaxe complète -->
<a v-bind:href="url"> ... </a>
<!-- abréviation -->
<a :href="url"> ... </a>
<!-- abréviation avec argument dynamique (2.6.0+) -->
<a :[key]="url"> ... </a>
<!-- syntaxe complète -->
<a v-on:click="doSomething"> ... </a>
<!-- abréviation -->
<a @click="doSomething"> ... </a>
<!-- abréviation avec argument dynamique (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
<div>
{{ message.split('').reverse().join('') }} <!-- #1 Simplicité -->
</div>
...
<div>
{{ message.split('').reverse().join('') }} <!-- #2 Performance -->
</div>
...
<div>
{{ message.split('').reverse().join('') }} <!-- #3 Répétition -->
</div>
<div id="app">
<p>
Message original : {{ message }}
</p>
<p>
Message inversé calculé : {{ reversedMessage }}
</p>
</div>
var vm = new Vue({
el: '#app',
data: {
message: 'Bonjour'
},
computed: {
// un accesseur (getter) calculé
reversedMessage: function () {
// `this` pointe sur l'instance vm
return this.message
.split('')
.reverse()
.join('')
}
}
})
LIVE
computed: {
now: function () {
return Date.now()
}
}
methods: {
reverseMessage: function () {
return this.message.split('').reverse().join('')
}
}
Différence : computed met en cache son résultat
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
vm.fullName = 'John Doe';
// =>
vm.firstName; // 'John'
vm.lastName; // 'Doe'
computed: {
fullName: {
// accesseur
get: function () {
return this.firstName + ' ' + this.lastName
},
// mutateur
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
<div v-bind:class="{ active: isActive }"></div>
<div :class="{ active: isActive }"></div>
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
data: {
isActive: true,
hasError: false
}
<!-- Résultat -->
<div
class="static active">
</div>
<div :class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
<!-- Résultat -->
<div
class="active">
</div>
<div :class="classObject"></div>
data: {
isActive: true,
error: null
},
computed: {
classObject() {
return {
active: this.isActive && !this.error
}
}
}
<!-- Résultat -->
<div
class="active">
</div>
<div v-bind:class="['staticClass', dynamicClass]"></div>
data: {
dynamicClass: 'foo',
}
<!-- Résultat -->
<div
class="staticClass foo">
</div>
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
<!-- Résultat -->
<p class="foo bar baz boo">Hi</p>
<my-component
class="baz boo"
></my-component>
data: {
activeColor: 'red',
fontSize: 30
}
<!-- Résultat -->
<div style="color: red; font-size: 30px;"></div>
<div
v-bind:style="{
color: activeColor,
fontSize: fontSize + 'px'
}"
></div>
<div v-bind:style="[baseStyles, overridingStyles]"></div>
<div v-bind:style="{transform: 'translate(120px, 50%)'}"></div>
Préfixage automatique selon le naviguateur
<h1 v-if="awesome">Vue est extraordinaire !</h1>
<h1 v-if="awesome">Vue est extraordinaire !</h1>
<h1 v-else>Oh non 😢</h1>
<h1 v-if="awesome">Vue est extraordinaire !</h1>
<h1 v-else-if="nice">Vue est sympa.</h1>
<h1 v-else>Oh non 😢</h1>
<template v-if="ok">
<h1>Titre</h1>
<p>Paragraphe 1</p>
<p>Paragraphe 2</p>
</template>
<div v-if="user.connected" v-for="user in users">
{{ user.pseudo }}
</div>
<h1 v-show="ok">Bonjour !</h1>
v-show effectue toujours le rendu :
il permute la propriété CSS display
<template v-if="loginType === 'username'">
<label>Nom d'utilisateur</label>
<input placeholder="Entrez votre nom d'utilisateur">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Entrez votre adresse email">
</template>
Contrôler la ré-utilisabilité d'un élément dans le rendu
<template v-if="loginType === 'username'">
<label>Nom d'utilisateur</label>
<input placeholder="Entrez votre nom d'utilisateur" key="name">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Entrez votre adresse email" key="email">
</template>
Contrôler la ré-utilisabilité d'un élément dans le rendu
<div class="foo" :class="{ bar: displayBar, baz, qux: false }"></div>
data: {
foo: false,
bar: true,
displayBar: false,
baz: false,
qux: true
}
<div :style="{ backgroundColor, color: activeColor }"></div>
data: {
activeColor: 'red',
color: 'blue',
fontSize: 30,
backgroundColor: 'white'
}
<ul id="app">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var app = new Vue({
el: "#app",
data: {
items: [
{ message: "Foo" },
{ message: "Bar" }
]
}
});
<ul id="app">
<li v-for="(item, index) in items">
#{{index}} {{ item.message }}
</li>
</ul>
var app = new Vue({
el: "#app",
data: {
items: [
{ message: "Foo" },
{ message: "Bar" }
]
}
});
<ul id="app">
<li v-for="(value, key) in book">
{{key}} : {{ value }}
</li>
</ul>
var app = new Vue({
el: "#app",
data: {
book: {
title: "Learn Vue",
author: "Jane Doe",
publishedAt: "2016-04-10"
}
}
});
<ul id="app">
<li v-for="(value, key, index) in book">
#{{index}} {{key}} : {{ value }}
</li>
</ul>
var app = new Vue({
el: "#app",
data: {
book: {
title: "Learn Vue",
author: "Jane Doe",
publishedAt: "2016-04-10"
}
}
});
⚠️ L'ordonnancement des propriétés d'un objet JavaScript n'est pas garantie
<div id="app">
<input
:placeholder="'#' + i + ' - ' + item"
v-for="(item, i) in items"
:key="i"
/>
<button @click="remove">Remove first</button>
</div>
var app = new Vue({
el: "#app",
data: {
items: [4, 5, 9]
},
methods: {
remove() {
this.items.shift();
}
}
});
Définir key lors de l'usage de v-for est recommandé par Vue
<div id="app">
<input
:placeholder="'#' + i + ' - ' + item"
v-for="(item, i) in items"
:key="item"
/>
<button @click="remove">Remove first</button>
</div>
var app = new Vue({
el: "#app",
data: {
items: [4, 5, 9]
},
methods: {
remove() {
this.items.shift();
}
}
});
<div id="app" @click="sort">
{{ letters }}
</div>
var app = new Vue({
el: "#app",
data: {
letters: ["J", "P", "B", "G"]
},
methods: {
sort() {
this.letters.sort();
}
}
});
Méthodes de mutations réactives
push(), pop(), shift(), unshift(),
splice(), sort(), reverse()
<ul id="app">
<li v-for="n in even">{{ n }}</li>
</ul>
var app = new Vue({
el: "#app",
data: {
numbers: [1, 2, 3, 4, 5]
},
computed: {
even() {
return this.numbers.filter(
(n) => n % 2 === 0
);
}
}
});
via une propriété calculée
<ul id="app">
<li v-for="n in even()">
{{ n }}
</li>
</ul>
var app = new Vue({
el: "#app",
data: {
numbers: [1, 2, 3, 4, 5]
},
methods: {
even() {
return this.numbers.filter(
(n) => n % 2 === 0
);
}
}
});
via une méthode
<my-component v-for="item in items" :key="item.id"></my-component>
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
<div id="app">
<button @click="counter += 1">
Incrémenter
</button>
<p>Compteur : {{ counter }}</p>
</div>
var app = new Vue({
el: "#app",
data: {
counter: 0
}
});
<div id="app">
<button @click="increment">
Incrémenter
</button>
<p>Compteur : {{ counter }}</p>
</div>
var app = new Vue({
el: "#app",
data: {
counter: 0
},
methods: {
increment() {
this.counter += 2;
}
}
});
<div id="app">
<button @click="increment(3)">
Incrémenter
</button>
<p>Compteur : {{ counter }}</p>
</div>
var app = new Vue({
el: "#app",
data: {
counter: 0
},
methods: {
increment(delta) {
this.counter += delta;
}
}
});
<div id="app">
<p @click="log('p#1@click')">
<input type="checkbox" @click="log('input#1@click')" /> @click
</p>
<p @click="log('p#2@click')">
<input type="checkbox" @click.stop="log('input#2@click.stop')" />
@click.stop
</p>
<p>
<input type="checkbox" @click.prevent="log('input#3@click.prevent')" />
@click.prevent
</p>
<p>
<input type="checkbox" @click.once="log('input#3@click.once')" />
@click.once
</p>
<p>
<input
type="checkbox"
@click.prevent.once="log('input#4@click.prevent.once')"
/>
@click.prevent.once
</p>
<p>Events : {{ events }}</p>
<button @click="events = []">Clear</button>
</div>
var app = new Vue({
el: "#app",
data: {
events: []
},
methods: {
log(e) {
this.events.push(e);
}
}
});
Modifient le traitement de l'évènement
Générique | Clavier | Système | Souris |
---|---|---|---|
.stop .prevent .capture .self .once .passive |
.enter .tab .delete .esc .space .up .down .left .right |
.ctrl .alt .shift .meta .exact |
.left .right .middle |
Créer une todo list dans laquelle on puisse marquer les éléments comme supprimés.
Permettre trois modes de visualisation : "Tout", "Actifs", "Supprimés"
Rajouter un champ de recherche pour filtrer la liste des tâches.
Créer des liaisons bi-directionnelles entre le modèle et la vue.
<div id="app">
<input v-model="message" />
<p>{{ message }}</p>
</div>
var app = new Vue({
el: "#app",
data: {
message: ""
}
});
<input v-model="message" />
<input
:value="message"
@input="message = $event.target.value"
/>
<div id="app">
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message"></textarea>
</div>
var app = new Vue({
el: "#app",
data: {
message: ""
}
});
<div id="app">
<input
type="checkbox"
id="checkbox"
v-model="checked"
/>
<label for="checkbox">{{ checked }}</label>
</div>
var app = new Vue({
el: "#app",
data: {
checked: true
}
});
<div id="app">
<input type="radio" id="one" value="Un" v-model="picked" />
<label for="one">Un</label><br />
<input type="radio" id="two" value="Deux" v-model="picked" />
<label for="two">Deux</label><br />
<span>Choisi : {{ picked }}</span>
</div>
var app = new Vue({
el: "#app",
data: {
picked: null
}
});
<div id="app">
<select v-model="selected">
<option disabled value="">Choisissez</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Sélectionné : {{ selected }}</span>
</div>
var app = new Vue({
el: "#app",
data: {
selected: ""
}
});
<div id="app">
<select v-model="selected" multiple>
<option disabled value="">Choisissez</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Sélectionné : {{ selected }}</span>
</div>
var app = new Vue({
el: "#app",
data: {
selected: []
}
});
<div id="app">
<select v-model="selected">
<option
v-for="option in options"
:value="option.value"
>
{{ option.text }}
</option>
</select>
<span>Sélectionné : {{ selected }}</span>
</div>
var app = new Vue({
el: "#app",
data: {
selected: "A",
options: [
{ text: "Un", value: "A" },
{ text: "Deux", value: "B" },
{ text: "Trois", value: "C" }
]
}
});
<!-- `picked` sera une chaine de caractères "a" quand le bouton radio sera sélectionné -->
<input type="radio" v-model="picked" value="a">
<!-- `toggle` est soit true soit false -->
<input type="checkbox" v-model="toggle">
<!-- `selected` sera une chaine de caractères "abc" quand la première option sera sélectionnée -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
<div id="app">
<p>
v-model.lazy
<input v-model.lazy="lazy" />
<code>{{ lazy }}</code>
</p>
<p>
v-model.number
<input v-model.number="number" type="number" />
<code>{{ typeof number }}</code>
<code>{{ number }}</code>
</p>
<p>
v-model.trim
<input v-model.trim="trim" />
<code>{{ trim }}</code>
</p>
</div>
var app = new Vue({
el: "#app",
data: {
lazy: "",
number: null,
trim: ""
}
});
Il est possible d'implémenter un v-model personnalisé pour ses composants.
Cela passe par la déclaration d'une prop de modèle (par défaut "value") et d'un événement de modèle (par défaut @input).
Mais on voit tout cela un peu plus tard ;)
vue
<template>
modèle
data
logique
methods
@event
this.datum = ...
render
var app = new Vue({
});
var app = new Vue({
el: "#app"
});
var app = new Vue({
el: "#app",
template: "..."
});
var app = new Vue({
el: "#app",
template: "...",
data: { ... }
});
var app = new Vue({
el: "#app",
template: "...",
data: { ... },
computed: { ... }
});
var app = new Vue({
el: "#app",
template: "...",
data: { ... },
computed: { ... },
methods: { ... }
});
var app = new Vue({
el: "#app",
template: "...",
data: { ... },
computed: { ... },
methods: { ... },
watch: { ... }
});
var app = new Vue({
el: "#app",
template: "...",
data: { ... },
computed: { ... },
methods: { ... },
beforeCreated() { ... },
created() { ... },
beforeMounted() { ... },
mounted() { ... },
updated() { ... },
destroyed() { ... }
});
<div id="app">
<button-counter></button-counter>
</div>
Vue.component("button-counter", {
name: "ButtonCounter",
data() {
return {
count: 0
};
},
template: '<button v-on:click="count++">{{ count }} clicks</button>'
});
var app = new Vue({
el: "#app"
});
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
Vue.component("button-counter", {
name: "ButtonCounter",
data() {
return {
count: 0
};
},
template: '<button v-on:click="count++">{{ count }} clicks</button>'
});
var app = new Vue({
el: "#app"
});
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
Vue.component("button-counter", {
name: "ButtonCounter",
data() {
return {
count: 0
};
},
template: '<button v-on:click="count++">{{ count }} clicks</button>'
});
var app = new Vue({
el: "#app"
});
// kebab-case
Vue.component("button-counter", {
...
});
// PascalCase
Vue.component("ButtonCounter", {
...
});
<div id="app">
<button-counter></button-counter>
<ButtonCounter></ButtonCounter>
</div>
let ComponentA = { /* ... */ }
let ComponentB = { /* ... */ }
// Dans l'instance de Vue
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
// Dans un composant
let ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
// Dans l'instance de Vue
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
// Dans un composant
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
Les props
<!-- kebab-case en HTML -->
<blog-post post-title="Hello !"></blog-post>
<BlogPost post-title="Hello !"></BlogPost>
Vue.component('blog-post', {
// camelCase en JavaScript
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<blog-post title="Mon initiation avec Vue"></blog-post>
<blog-post title="Blogger avec Vue"></blog-post>
<blog-post title="Pourquoi Vue est tellement cool"></blog-post>
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
></blog-post>
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
new Vue({
el: '#app',
data: {
posts: [
{ id: 1, title: 'Mon initiation avec Vue' },
{ id: 2, title: 'Blogger avec Vue' },
{ id: 3, title: 'Pourquoi Vue est tellement cool' }
]
}
})
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
></blog-post>
// Template du composant
<h3>{{ title }}</h3>
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
:content="post.content"
></blog-post>
// Template du composant
<h3>{{ title }}</h3>
<div v-html="content"></div>
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
:content="post.content"
></blog-post>
// Template du composant
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
// Un composant doit avoir
// un unique élément racine
!!!
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
:content="post.content"
:publishedAt="post.publishedAt"
:comments="post.comments"
></blog-post>
<blog-post
v-for="post in posts"
:key="post.id"
:post="post"
></blog-post>
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function, // déprécié
contactsPromise: Promise
// tout autre constructeur
}
<blog-post
title="Lorem ipsum"
:likes="3"
:isPublished="false"
:commentIds="[1,2,3]"
:author="{name: 'Alice'}"
:callback="() => alert('Done')"
></blog-post>
props: {
propA: Number,
propB: [String, Number],
propC: {
type: String,
required: true
}
}
props: {
propD: {
type: Number,
default: 100
},
propE: {
type: Object,
default: () => ({ message: 'hello' })
},
propF: {
type: Array,
default: () => []
}
}
props: {
propG: {
validator: value => ['A', 'B', 'C'].includes(value)
}
}
Les événements
// Template racine
<blog-post
v-for="post in posts"
:key="post.id"
:post="post"
></blog-post>
// Template de <blog-post>
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
<button>
Like
</button>
</div>
// Remonter le like ?
// Template racine
<blog-post
v-for="post in posts"
:key="post.id"
:post="post"
@like="receiveLike"
></blog-post>
// Template de <blog-post>
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
<button @click="$emit('like')">
Like
</button>
</div>
methods: {
receiveLike() {
...
}
}
// Template racine
<blog-post
v-for="post in posts"
:key="post.id"
:post="post"
@note="receiveNote"
></blog-post>
// Template de <blog-post>
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
<button @click="$emit('note', 5)">
Noter bien
</button>
<button @click="$emit('note', 0)">
Noter pas bien
</button>
</div>
methods: {
receiveNote(payload) {
payload; // 0 ou 5
...
}
}
// Template racine
<blog-post
v-for="post in posts"
:key="post.id"
:post="post"
@note="receiveNote"
></blog-post>
// Template de <blog-post>
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
<button @click="love">Love</button>
<button @click="hate">Hate</button>
</div>
methods: {
receiveNote(note) {
...
}
}
methods: {
love() {
this.$emit('note', 5);
}
}
Props + Événements = ❤️
<blog-post-writer v-model="post">
</blog-post-writer>
// Comment faire ?
var vm = new Vue({
el: "#app",
data: {
post: "Hello world !"
}
});
<blog-post-writer v-model="post">
</blog-post-writer>
var vm = new Vue({
el: "#app",
data: {
post: "Hello world !"
}
});
<blog-post-writer
:value="post"
@input="post = $event">
</blog-post-writer>
=
var vm = new Vue({
el: "#app",
data: {
post: "Hello world !"
}
});
Vue.component('blog-post-writer', {
props: ['value'],
template: `
<p>
<input
:value="value"
@input="$emit('input',
$event.target.value)">
</p>`
})
<blog-post-writer v-model="post">
</blog-post-writer>
var vm = new Vue({
el: "#app",
data: {
post: "Hello world !"
}
});
<blog-post-writer :content.sync="post">
</blog-post-writer>
Vue.component('blog-post-writer', {
props: ['content'],
template: `
<p>
<input
:value="content"
@input="$emit('update:content',
$event.target.value)">
</p>`
})
Les slots
<alert-box>
Quelque chose s'est mal passé.
</alert-box>
<alert-box>
Quelque chose s'est mal passé.
</alert-box>
Vue.component('alert-box', {
template: `
<div class="alert-box">
<strong>Erreur !</strong>
<slot></slot>
</div>
`
})
<alert-box>
Quelque chose s'est mal passé.
<template #code>1337</template>
</alert-box>
Vue.component('alert-box', {
template: `
<div class="alert-box">
<strong>Erreur !</strong>
<slot></slot>
<i v-if="$slots.code"> Code <slot name="code" /> </i>
</div>
`
})
Single File Component
<component v-bind:is="customAlertComponent"></component>
Vue.component('success-alert', { ... });
Vue.component('error-alert', { ... });
var app = new Vue({
el: "#app",
data: {
customAlertComponent: 'success-alert'
}
});
Réaliser un tableau de score dont chaque ligne doit être un composant <TeamCounter>
Cahier des charges de TeamCounter :
Template voir ci-contre
Props
score.sync : Le score actuel de l'équipe
city: La ville de l'équipe
color: La couleur de l'équipe
Slots
#default: Le nom de l'équipe
Events
@update:score
@win : Déclencher lorsque le score dépasse
30, contient la ville de l'équipe en payload
// Store Vuex
{
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... }
};
// Composant Vue
{
data: { ... },
computed: { ... },
methods: { ... }
};
// Store Vuex
{
state: { prixHT: 10 },
getters: { ... },
mutations: { ... },
actions: { ... }
};
// Composant Vue
import { mapState } from 'vuex';
{
computed: {
...mapState(["prixHT"])
},
methods: { ... }
};
<template>
<button> {{ prixHT }} € </button>
</template>
// Store Vuex
{
state: { count: 10 },
getters: {
prixTTC(state) {
return state.prixHT * 1.2
}
},
...
};
// Composant Vue
import { mapGetters, ~} from 'vuex'
{
computed: {
...mapState(["prixHT"]),
...mapGetters(["prixTTC"])
},
methods: { ... }
};
<template>
<button> {{ prixHT }} € HT </button> {{ prixTTC }} € TTC
</template>
// Store Vuex
{
...
mutations: {
increment(state) {
state.prixHT += 5;
}
}
...
};
// Composant Vue
import {mapMutations} from 'vuex'
{
computed: {
...mapState(["prixHT"]),
...mapGetters(["prixTTC"])
},
methods: {
...mapMutations(["increment"])
}
};
<template>
<button @click="increment"> {{ prixHT }} € HT </button> {{ prixTTC }} € TTC
</template>
// Store Vuex
{
...
mutations: {
increment(state, payload) {
state.prixHT += payload;
}
}
...
};
// Composant Vue
import {mapMutations} from "vuex"
{
computed: {
...mapState(["prixHT"]),
...mapGetters(["prixTTC"])
},
methods: {
...mapMutations(["increment"])
}
};
<template>
<button @click="increment(7)"> {{ prixHT }} € HT </button> {{ prixTTC }} € TTC
</template>
// Store Vuex
{
mutations: {
increment(state, payload) {
state.prixHT += payload;
}
}
actions: {
increase(context) {
context.commit('increment', 3);
}
}
};
// Composant Vue
import { mapActions, ~} from 'vuex'
{
computed: {
...mapState(["prixHT"]),
...mapGetters(["prixTTC"])
},
methods: {
...mapMutations(["increment"]),
...mapActions(["increase"])
}
};
<template>
<button @click="increase"> {{ prixHT }} € HT </button> {{ prixTTC }} € TTC
</template>
// Store Vuex
{
actions: {
increase(context, payload) {
setTimeout(() => {
context.commit('increment', payload);
}, 3000)
}
}
};
// Composant Vue
{
computed: {
...mapState(["prixHT"]),
...mapGetters(["prixTTC"])
},
methods: {
...mapMutations(["increment"]),
...mapActions(["increase"])
}
};
<template>
<button @click="increase(4)"> {{ prixHT }} € HT </button> {{ prixTTC }} € TTC
</template>
// Store Vuex
{
actions: {
increase(context, payload) {
context.commit("mutation", payload);
context.dispatch("actions", payload);
context.state;
context.getters;
}
}
};
// Composant Vue
{
computed: {
...mapState(["prixHT"]),
...mapGetters(["prixTTC"])
},
methods: {
...mapMutations(["increment"]),
...mapActions(["increase"])
}
};
<template>
<button @click="increase(4)"> {{ prixHT }} € HT </button> {{ prixTTC }} € TTC
</template>
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import store from "store.js";
store.commit("mutation", payload);
store.dispatch("actions", payload);
store.state;
store.getters;
// store.js
import Vuex from 'vuex'
export default new Vuex.store({
state: ...,
getters: ...,
mutations: ...,
actions: ...
});
myAction(context, payload) {
return new Promise(...);
}
store.dispatch('myAction', "...")
.then(result => { ... })
.catch(error => { ... })
Si vous êtes bloqués, manifester vous
Github Issue / iut@chazelle.pro
https://github.com/benjaminchazelle/teach-vue-chat-client-starter
https://codesandbox.io/s/happy-pike-9kggu?file=/ROADMAP.md