Mickaël BARON - Version 09-2023
Creative Commons
Contrat Paternité
Partage des Conditions Initiales à l'Identique
2.0 France
creativecommons.org/licenses/by-sa/4.0Pas le temps ⏳ de tout traiter
Expérimentation via des exercices
Prérequis minimum
3.x
Origine
Historique des versions : version actuelle 3
Source : stateofjs.com
Source : vue2-introduction
Mon site
Le site de référence
<!-- Chargement de la bibliothèque -->
<script src="https://unpkg.com/vue@3"/>
<div id="helloworld">
<input v-model="value" type="text" />
<p>{{ value }}</p>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup () {
const value = ref('Helloworld')
return { value }
}}).mount('#helloworld');
</script>
Dossiers helloworldv3-sfc (version SFC) et helloworldv3 (version sans outil)
{{value}}
Deux utilisations différentes
Quand ?
Comment ?
<script src="https://unpkg.com/vue@3">
Avec quoi ?
Exemples
Pour chaque utilisation une écriture différente basée sur l'API Composition
Bibliothèque
<html>
<body>
<script src="https://unpkg.com/vue@3"></script>
<div id="helloworld">
<input v-model="value" type="text" />
<p>{{ value }}</p>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const value = ref('HelloWorld')
return { value }
}
}).mount('#helloworld');
</script>
<body>
<html>
Framework
<script setup>
import { ref } from 'vue'
const value = ref('HelloWorld')
</script>
<template>
<div id="helloworld">
<input v-model="value" type="text" />
<p>{{ value }}</p>
</div>
</template>
<style>
#helloworld {
text-align: center;
text-decoration: underline;
}
</style>
Inspiration du modèle d'architecture MVVM
Model-View-ViewModel
Vue.js correspond à la partie ViewModel
HelloWorld et le modèle d'architecture MVVM
Différents concepts clés de Vue.js étudiés dans la suite
{{ value }}
v-model="value"
value
La vue est rafraichie à chaque fois que les propriétés du composant changent
Utilisation de propriétés reactives qui peuvent notifier la vue de changements
Deux mots clés disponibles fournies par Vue.js
ref
: pour les valeurs primitivesreactive
: pour les objetsNécessite d'importer ces mots clés pour les utiliser
Quand ?
Comment déclarer ?
const property = ref('Helloworld')
Comment accéder ou modifier ?
value
) de la
référence qui est modifiée ou obtenue
property.value
<div id="app">
<input v-model="myprop" type="text" />
<p>{{myprop}}</p>
</div>
<script>
// Importation du mot clé ref
const { createApp, ref } = Vue
createApp({
setup() {
const myprop = ref('HelloWorld')
// Lecture de la valeur de myprop
console.log(myprop.value)
// Modification de la valeur de myprop
myprop.value = "HelloWorld2"
return { myprop }
}
}).mount('#app');
</script>
Fichier indexref.html (version sans outil)
{{value}}
Quand ?
Comment déclarer ?
const property = reactive({ value: 'Helloworld' })
Comment reactive
fonctionne ?
.value
toRef
et toRefs
pour transformer en
ref
<div id="app">
<input v-model="data.myprop" type="text" />
<p>{{data.myprop}}</p>
</div>
<script>
// Importation du mot clé reactive
const { createApp, reactive } = Vue
createApp({
setup() {
const data = reactive({ myprop: 'HelloWorld' })
// Lecture de la propriété myprop de data
console.log(data.myprop)
// Modification de la propriété myprop de data
data.myprop = "HelloWorld2"
return { data }
}
}).mount('#app');
</script>
Fichier indexreactive.html (version sans outil)
{{data.myprop}}
Une seule propriété réactive avec toRefs
<div id="app">
<input v-model="myprop1" type="text" />
<p>{{myprop1}}</p>
<input v-model="myprop2" type="text" />
<p>{{myprop2}}</p>
</div>
<script>
// Importation du mot clé toRefs
const { createApp, reactive, toRefs } = Vue
createApp({
setup() {
const data = reactive({
myprop1: 'HelloWorld1',
myprop2: 'HelloWorld2' })
return { ...toRefs(data) }
}
}).mount('#app');
</script>
Fichier indexreactivetorefs.html (version sans outil)
{{myprop1}}
{{myprop2}}
Comment réaliser le databinding ?
Interpolation
Directive
Liaison unidirectionnelle : modèle ➡️ vue
Utilisation de la notation moustache : {{ ... }}
{{ myProperty }}
{{ number + 1 }}
{{ ok ? 'OUI' : 'NON' }}
{{ message.split('').reverse().join('') }}
Limites
v-html
)v-bind
)
Attributs HTML spécifiques à Vue.js
v-model
v-bind
v-if
v-else
v-else-if
v-show
v-for
v-on
v-html
v-text
v-cloak
v-pre
v-once
Étudions ces directives par l'exemple
Liaison bidirectionnelle (modèle ↔️ vue) pour composants
<input> <select> <textarea>
<div id="app">
<input v-model="value"/>
<textarea v-model="value"/>
<p>{{value}}</p>
</div>
<script>
createApp({
setup() {
const value = ref('Helloworld')
return { value }
}
}).mount('#app')
</script>
Fichier vmodel.html (version sans outil)
{{value}}
Liaison unidirectionnelle (modèle ➡️ vue) utilisable dans des
attributs HTML (ex. : value
,
disabled
)
Syntaxe 👉 v-bind:attribute...
Syntaxe simplifiée 👉 :attribute...
<div id="app">
<input v-model="value" />
<input v-bind:value="value" />
<input :value="value" />
<p>{{value}}</p>
</div>
<script>
createApp({
setup() {
const value = ref('Helloworld')
return { value }
}}).mount('#app')
</script>
Fichier vbind.html (version sans outil)
{{value}}
Cacher ou afficher un élément HTML
v-if, v-else et v-else-if
v-else
et v-else
obligatoirement après
v-if
v-show
display
Comment choisir entre v-show
et v-if
?
v-show
<div id="app">
<input v-model="show">
Afficher Vue.js
<img v-show="show" src="lias.png"/>
<img v-if="show" src="vue.svg"/>
<img v-else src="mbaron.jpg"/>
</div>
<script>
createApp({
setup() {
const show = ref(true)
return { show }
}
}).mount('#app')
</script>
Fichier vifvshow.html (version sans outil)
Effectuer plusieurs fois le rendu d'une liste d'éléments
Tableau (items)
v-for="(item, index) in items"
rouge
)1
)Objet (object)
v-for="(value, name, index) in object"
mickaël
)
firstname
)
<div id="app">
<p v-for="(val,index) in tab">
{{val + "," + index}}
</p>
<p v-for="(val,name,index) in obj">
{{val + "," + name + "," + index}}
</p>
</div>
<script>
createApp({
setup() {
const tab = ref(['rouge','bleu'...])
const obj = ref({p1:'val1',p2:'val2'...})
return { tab, obj }
}
}).mount('#app')
</script>
Fichier vfor.html (version sans outil)
{{ val+","+index }}
{{ val + "," + name + "," + index }}
Écouter les événements de la partie View
pour
éxécuter vos traitements
Syntaxe 👉 v-on:event=...
Syntaxe simplifiée 👉 @event=...
Modificateurs d'événements
.stop
: un évènement stoppé.prevent
: rechargement page stoppé.left
: uniquement bouton gauche de la souris
.once
: une seule fois<div id="app">
<button v-on:click="increase">
Augmenter
</button>
<button @click.once="decrease">
Diminuer
</button>
<p>{{ value }}</p>
</div>
<script>
createApp({
setup() {
const value = ref(0)
function increase() {
value.value++;
}
function decrease() {
value.value--;
}
return { value, increase, decrease }
}
}).mount('#app')
</script>
Fichier von.html (version sans outil)
{{ value }}
v-html
: interprète du contenu HTML
v-text
: identique à l'interpolation de texte
{{ }}
v-once
: effectue le rendu une seule fois
v-cloak
: bloque l'affichage tant que la
compilation
Vue.js n'est
pas terminée
v-pre
: n'effectue pas de compilation, texte brute
v-???
: possibilité de créer ses propres directives
personnalisées :
en savoir plus
<div id="app">
<div v-html="html"></div>
<div v-text="html"></div>
<div v-once>{{value}}</div>
<input v-model="value" />
<div v-pre>{{value}}</div>
</div>
<script>
createApp({
setup() {
const html = ref('<b>HelloWorld</b>')
const value = ref('HelloWorld')
return { html, value }
}
}).mount('#app-directives');
</script>
Fichier vhtmltextoncepre.html (version sans outil)
Mise en place d'un composant ?
Deux manières de développer des composants
Bibliothèque
<html>
<body>
<script src="https://unpkg.com/vue@3"></script>
<div id="helloworld">
<input v-model="value" type="text" />
<p>{{ value }}</p>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const value = ref('HelloWorld')
return { value }
}
}).mount('#helloworld');
</script>
<body>
<html>
Framework
<script setup>
import { ref } from 'vue'
const value = ref('HelloWorld')
</script>
<template>
<div id="helloworld">
<input v-model="value" type="text" />
<p>{{ value }}</p>
</div>
</template>
<style>
#helloworld {
text-align: center;
text-decoration: underline;
}
</style>
Fichier Single-File-Component via format .vue
Script : ViewModel
Template : View
Style
<script setup>
import { ref } from 'vue'
const value = ref('HelloWorld')
</script>
<template>
<div id="helloworld">
<input v-model="value" type="text" />
<p>{{ value }}</p>
</div>
</template>
<style>
#helloworld {
text-align: center;
text-decoration: underline;
}
</style>
Deux étapes à réaliser depuis un fichier .vue
① Importation (dans la partie script
)
import YourComponent1 from "./components/yourcomponent1.vue"
import YourComponent2 from "./components/yourcomponent2.vue"
...
② Instanciation (dans la partie template
)
<YourComponent1></YourComponent1>
<YourComponent2></YourComponent2>
<script setup>
import { ref } from 'vue'
import Clock from './components/Clock.vue'
const value = ref("HelloWorld")
</script>
<template>
<Clock></Clock>
<input v-model="value" type="text" />
<p>{{ value }}</p>
</template>
<style/>
Dossiers customelementv3-sfc (version SFC) et customelementv3 (version sans outil)
{{ value }}
Problématiques
View
Propriété calculée (computed)
Syntaxe
const yourProperty = computed(() => {
// Votre code qui dépend d'autres propriétés du composant
}
)
<script setup>
import { ref, computed } from 'vue'
const value = ref('HelloWorld')
const messageLength = computed(() => {
return value.value.length
})
</script>
<template>
<input v-model="value" type="text"/>
<p>{{ value }}</p>
<p>Longueur {{ messageLength }}</p>
</template>
<style/>
Dossiers computedv3-sfc (version SFC) et computedv3 (version sans outil)
{{ value }}
Longueur : {{ messageLength }}
Possibilité de faire des mutateurs calculés (v-model
)
<script setup>
import { ref, computed } from 'vue'
const value = ref('HelloWorld')
const writableValue = computed({
get() {
return value.value;
},
set(newValue) {
if (newValue.length <= 10) {
value.value = newValue;
}
}
})
</script>
<template>
<input v-model="writableValue" />
<p>{{ value }}</p>
</template>
Dossiers computedwritablev3-sfc (version SFC) et computedwritablev3 (version sans outil)
{{ value }}
Problématique
Observateur (watch et watchEffect)
Syntaxe
watch(value, (newValue, oldValue) => {
// Votre code
})
<script setup>
import { ref, watch } from 'vue'
const value = ref('')
const message = ref('Parfait')
watch(value, (newValue, oldValue) => {
if (oldValue.length > 10) {
message.value = "Trop Grand"
} else {
message.value = "Parfait"
}
})
</script>
<template>
<input v-model="value" type="text" />
<p>{{ message }}</p>
</template>
<style/>
Dossiers watchv3-sfc (version SFC) et watchv3 (version sans outil)
{{ message }}
<script setup>
import { ref, watchEffect } from 'vue'
const val1 = ref('')
const val2 = ref('')
const text = ref('')
const stop = watchEffect(() =>
text.value = val1.value + " " + val2.value
)
function stopWatch() { stop() }
</script>
<template>
<div id="app">
<input v-model="val1" type="text" />
<input v-model="val2" type="text" />
<p>{{ text }}</p>
<button @click="stopWatch()">Stop</button>
</div>
</template>
<style/>
Dossiers watcheffectv3-sfc (version SFC) et watcheffectv3 (version sans outil)
{{ text }}
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeUmount
Umounted
Comment déclencher du code à certaines étapes ?
Utilisation de fonctions Hook (avant/après)
setup
: créationonBeforeMount/onMounted
: ajout dans le DOM
onBeforeUpdate/onUpdated
: modification
(propriété, DOM)
onBeforeUmount/onUmounted
: desctructiononErrorCaptured
: capture des erreurs<script setup>
import { createApp,ref,onBeforeMount... } from 'vue'
display("before Create")
const value = ref('HelloWorld')
onBeforeMount(() => {
display("Before Mount: " + value.value)
})
onMounted(() => {
display("Mounted: " + value.value)
})
display("after Created: " + value.value)
</script>
<template>
<div id="app">
<input v-model="value" type="text" />
<p>{{ value }}</p>
<p>Afficher la console</p>
</div>
</template>
<style/>
Dossiers lifecyclev3-sfc (version SFC) et lifecyclev3 (version sans outil)
{{ value }}
👇 Display the console
Différents contenus pour vos composants
computed
: propriétés calculéeswatch
: propriétés observéesprops
Parent ➡️ Enfant direct ?
Parent ➡️ Petit-enfant ?
Enfant ➡️ Parent ?
Parent ↔️ Enfant ?
Globalement 🌎 ?
En nommant explicitement 🫵 ?
Permet une communication Parent ➡️ Enfant direct
Principe
Valeurs : statique et réactive
<script setup>
import { ref } from 'vue'
const clock = ref(new Date().toLocaleTimeString())
const props = defineProps({ message: String })
setInterval(() => {
computeClock();
}, 1000);
function computeClock() {
clock.value = new Date().toLocaleTimeString();
}
</script>
<template>
<div>{{ clock }} {{ message }}</div>
</template>
<style/>
Dossiers propsv3-sfc (version SFC) et propsv3 (version sans outil)
<script setup>
import { ref } from 'vue'
import Clock from './components/Clock.vue'
const value = ref("HelloWorld")
</script>
<template>
<Clock :message="value"></Clock>
<input v-model="value" type="text" />
<p>{{ value }}</p>
</template>
<style/>
Dossiers propsv3-sfc (version SFC) et propsv3 (version sans outil)
{{ value }}
Permet une communication Parent ➡️ Petit-Enfant
Pourquoi ?
Principe
Contrainte : hiérarchie obligatoire
<script setup>
import { ref, provide } from 'vue'
import Clock from './components/Clock.vue'
const value = ref("HelloWorld")
provide('message', value)
</script>
<template>
<Clock></Clock>
<div>
<input v-model="value" type="text"/>
</div>
</template>
Dossiers provideinjectv3-sfc (version SFC) et provideinjectv3 (sans outil)
<script setup>
import { ref } from 'vue'
import ClockDisplay from './ClockDisplay.vue'
const clock = ref(...)
setInterval(() => {
computeClock();
}, 1000);
function computeClock() { ... }
</script>
<template>
{{ clock }} <ClockDisplay></ClockDisplay>
</template>
Composant Clock : dossier provideinjectv3-sfc (version SFC) et provideinjectv3 (sans outil)
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
<template>
{{ message }}
</template>
Composant ClockDisplay : dossier provideinjectv3-sfc (version SFC) et provideinjectv3 (sans outil)
Permet une communication Enfant ➡️ Parent
Principe (gestion événement)
$emit
v-on
ou
@
Événement
<script setup>
import { ref } from 'vue'
const clock = ref(new Date().toLocaleTimeString())
defineEmits(['save-clock'])
setInterval(() => {
computeClock();
}, 1000);
function computeClock() {
clock.value = new Date().toLocaleTimeString();
}
</script>
<template>
<button v-on:click="$emit('save-clock', clock)">
{{clock}}
</button>
</template>
<style/>
Dossiers customeventv3-sfc (version SFC) et customeventv3 (version sans outil)
<script setup>
import { ref } from 'vue'
import Clock from './components/Clock.vue'
const value = ref("HelloWorld")
function saveClock(currentClock) {
value.value = currentClock;
}
</script>
<template>
<Clock @save-clock="saveClock($event)"/>
<input v-model="value" type="text" />
<p>{{ value }}</p>
</template>
<style/>
Dossiers customeventv3-sfc (version SFC) et customeventv3 (version sans outil)
{{value}}
Permet une communication Parent ↔️ Enfant
Principe
v-model
v-model
modelValue
update:modelValue
<script setup>
import { ref } from "vue"
defineProps({ modelValue })
defineEmits(["update:modelValue"])
</script>
<template>
<input :value="modelValue"
@input="$emit('update:modelValue',
$event.target.value)"/>
</template>
Composant FormInput customeventv3-sfc (version SFC) et customeventv3 (version sans outil)
<script setup>
import { ref, reactive } from "vue"
import FormInput from "./components/FormInput.vue"
const person = reactive({ name: "" })
</script>
<template>
<FormInput v-model="person.name"></Forminput>
<p>{{ person.name }}</p>
</template>
Composant parent customeventv3-sfc (version SFC) et customeventv3 (version sans outil)
{{ person.name }}
Permet des communications Parent ↔️ Enfant
Principe
v-model
v-model:prop_name
update:prop_name
<script setup>
import { ref } from "vue"
defineProps({ name, city })
defineEmits(["update:name", "update:city"])
</script>
<template>
<input :value="name" @input="$emit('update:name',..."/>
<input :value="city" @input="$emit('update:city',..."/>
</template>
Composant FormInput multiplecustomeventv3-sfc (version SFC) et multiplecustomeventv3 (version sans outil)
<script setup>
import { ref, reactive } from "vue"
import FormInput from "./components/FormInput.vue"
const person = reactive({ name: "", city: "" })
</script>
<template>
<FormInput
v-model:name="person.name"
v-model:city="person.city">
</Forminput>
<p>{{ person.name }} {{ person.city }}</p>
</template>
Composant parent multiplecustomeventv3-sfc (version SFC) et multiplecustomeventv3 (version sans outil)
{{ person.name }}
{{ person.city }}
Permet une communication globale 🌎
Principe (solution DIY)
Aller plus loin
import { reactive } from 'vue'
const debug = true
const state = reactive({
message: 'HelloWorld'
})
function setMessage(newValue) {
if (debug) console.log('setMessage triggered with ', newValue)
state.message = newValue
}
export function store() {
return {
state,
setMessage
}
}
Dossier statemanagerv3-sfc (version SFC) et statemanagerv3 (version sans outil)
<script setup>
import { computed } from 'vue'
import Writable from './components/Writable.vue'
import Readable from './components/Readable.vue'
import { store } from './store.js'
const { state, setMessage } = store()
const message = computed({
get() { return state.message },
set(newValue) { setMessage(newValue) }
})
</script>
<template>
<div><input v-model="message" type="text" />{{ message }}</div>
<div><Writable></Writable><Readable></Readable></div>
</template>
<style/>
Dossier statemanagerv3-sfc (version SFC) et statemanagerv3 (version sans outil)
<script setup>
import { store } from '../store.js'
const { state } = store()
</script>
<template>
{{ state.message }}
</template>
<style/>
Dossier statemanagerv3-sfc (version SFC) et statemanagerv3 (version sans outil)
<script setup>
import { store } from '../store.js'
const { state } = store()
</script>
<template>
<input v-model="state.message" type="text" />
</template>
<style/>
Dossier statemanagerv3-sfc (version SFC) et statemanagerv3 (version sans outil)
En nommant explicitement 🫵
Principe (parent ➡️ enfants)
ref
ref
Principe (enfant ➡️ parent)
Problème
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const refChild = ref(null)
const value = ref("")
function callParent(pValue) { value.value = pValue }
defineExpose({
callParent
})
</script>
<template>
<Child ref="refChild"></Child>
<button @click="refChild.callChild()">Go</button>
{{ value }}
</template>
<style/>
Dossier referencev3-sfc (version SFC) et referencev3 (version sans outil)
<script setup>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
function callChild() {
instance.proxy.$parent.callParent("Hello...");
}
defineExpose({
callChild
})
</script>
<template>
<div>Enfant</div>
</template>
<style/>
Technique | Usage | Avis |
---|---|---|
Props | Parent ➡️ Enfant | 👍 |
Provide/Inject | Parent ➡️ Petit-Enfant | 👍 |
Événement perso | Enfant ➡️ Parent | 👍 |
Gestionnaire états | Globalité 🌎 | 👍 |
En nommant 🫵 | Parent ↔️ Enfant | 👎👎 |
Outils pour le développeur
Vue DevTools permet d'introspecter votre application Vue.js depuis le navigateur
Installation
Outil pour créer des projets Vue.js avec le format .vue
Création d'un projet (create-vue basé sur Vite)
$ npm create vite@latest
Installation des dépendances
$ npm install
Exécution pour tester
$ npm run dev
Exécution pour déployer
$ npm run build
VuePress : générateur de site statique
Pinia : gestionnaire d'états
Nuxt.js : framework du framework Vue.js
Vue Test Utils : tests
Bibliothèques de composants
Disponibles sur Github : github.com/mickaelbaron
Intégration dans un existant
Single Page Application (SPA)