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 Creative Lab)
Déc 2013 - Version 0.6
Oct 2015 - Version 1.0
Sep 2016 - Version 2.0
Fév 2022 - 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>
// Plus usuellement
import { createApp } from "vue"
<div id="app"></div>
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount("#app")
Modèle
Vue
Logique
<script setup>
import { ref } from 'vue'
const message = ref("Hello world")
</script>
<template>
<h1>{{ message }}</h1>
</template>
<style scoped>
h1 {
text-decoration: underline;
}
</style>
<script setup>
import { ref, computed, watch } from "vue"
defineProps(['text'])
const textSize = computed(() =>
text.value.length
)
const sizeChanged = ref(0)
watch(textSize, () => sizeChanged.value++)
function reset {
sizeChanged.value = 0
}
</script>
<template>
{{ text }} | {{ mutationCount }}
</template>
<script>
export default {
props: ['text'],
data: () => ({
sizeChanged: 0
}),
computed: {
textSize() {
return this.text.length
}
},
watch: {
textSize() {
this.sizeChanged++
}
},
methods: {
reset() {
this.sizeChanged = 0
}
}
}
</script>
=
<script setup>
import { ref } from 'vue'
const message = ref("Hello world")
</script>
<template>
<h1>{{ message }}</h1>
</template>
<script setup>
import { ref } from 'vue'
const url = ref("http://...")
</script>
<template>
<img v-bind:src="url" />
</template>
<script setup>
import { ref } from "vue"
const seen = ref(true)
</script>
<template>
<p v-if="seen">Je suis visible</p>
<pre>
En console, entrer :
window.seen.value = false
</pre>
</template>
<script setup>
const verbs = [
{ text: 'Veni' },
{ text: 'Vidi' },
{ text: 'Vici' }
]
</script>
<template>
<ol>
<li v-for="verb in verbs"
:key="verb.text"
>
{{ verb.text }}
</li>
</ol>
</template>
<script setup>
import { ref } from "vue"
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<p>{{ count }}</p>
<button v-on:click="increment">
Incrémenter !
</button>
</template>
<script setup>
import { ref } from "vue"
const message = ref("Hello world !")
</script>
<template>
<input v-model="message" />
<p>{{ message }}</p>
</template>
// LightSaber.vue
<script setup>
</script>
<template>
<span>Yoda</span>
<span class="handle">|||||</span>
<span class="light">======</span>
</template>
<style scoped>
.handle {
display: inline-block;
background: grey;
}
.light {
display: inline-block;
background: lightgreen;
color: transparent;
}
</style>
<script setup>
import LightSaber from "./LightSaber.vue"
</script>
<template>
<LightSaber />
</template>
// LightSaber.vue
<script setup>
const props = defineProps({
owner: String,
color: String
})
</script>
<template>
<div>
<span>{{ owner }}</span>
<span class="handle">|||||</span>
<span class="light"
:style="{background: color}">
======
</span>
</div>
</template>
<style scoped>...</style>
<script setup>
import LightSaber from "./LightSaber.vue"
</script>
<template>
<LightSaber owner="Mace windu" color="magenta" />
</template>
<script setup>
import LightSaber from "./LightSaber.vue"
const jedis = [
{ name: 'Obi-Wan', color: 'lightblue' },
{ name: 'Darth Vader', color: 'red' },
{ name: 'Rey', color: 'orange' },
]
</script>
<template>
<LightSaber
v-for="jedi in jedis"
:owner="jedi.name"
:key="jedi.name"
:color="jedi.color"
/>
</template>
Créer une micro application permettant d'ajouter des éléments à une liste à l'aide d'un champ texte et d'un bouton.
<script setup>
import { onBeforeMount, ... } from "vue"
onBeforeMount(() => {
// La réactivité est en place
// mais le composant n'est pas monté
// au DOM
})
onBeforeUpdate(() => {
// Le DOM va être mis à jour suite
// à une mutation dans l'état local
})
onBeforeUnmount(() => {
// Le composant va être démonté du DOM
})
</script>
onMounted(() => {
// Le composant vient d'être monté au DOM
})
onUpdated(() => {
// Le DOM vient d'être mis à jour suite
// à une mutation dans l'état local
})
onUnmounted(() => {
// Le composant vient d'être démonté
// du DOM
})
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 :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-bind, v-on, 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>
Text
Donnés en tant que 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>
http://interactly.glitch.me
<div>
{{ message.split('').reverse().join('') }} <!-- #1 Sémantique difficile -->
</div>
...
<div>
{{ message.split('').reverse().join('') }} <!-- #2 Performance dégradée -->
</div>
...
<div>
{{ message.split('').reverse().join('') }} <!-- #3 Répétition évitable -->
</div>
<script setup>
import { ref, computed } from "vue"
const message = ref("Hello world")
const reversed = computed(() =>
message.value
.split('')
.reverse()
.join('')
)
</script>
<template>
<p>Message original : {{ message }}</p>
<p>Message inversé : {{ reversed }}</p>
</template>
const now = computed(() =>
Date.now()
)
<script setup>
// ...
const reverse = () =>
message.value
.split('')
.reverse()
.join('')
</script>
<template>
Message inversé : {{ reverse() }}
</template>
Différence : computed met en cache son résultat
import { ref, watch } from "vue"
const firstname = ref("John")
const lastname = ref("Doe")
const fullname = ref("John Doe")
watch([firstname, lastname], () =>
fullname.value = `${firstname.value} ${lastname.value}`
)
import { ref, computed } from "vue"
const firstname = ref("John")
const lastname = ref("Doe")
const fullname = computed(() =>
`${firstname.value} ${lastname.value}`
)
fullname.value= 'John Doe';
// =>
firstname.value; // 'John'
lastname.value; // 'Doe'
const fullname = computed({
get: () =>
`${firstname.value} ${lastname.value}`,
set: (_fullname) => {
[fn, ln] = _fullname.split(" ")
firstname.value = fn
lastname.value = ln
}
})
<div v-bind:class="{ active: isActive }"></div>
<div :class="{ active: isActive }"></div>
<script setup>
let isActive = ref(true)
let hasError = ref(false)
</script>
<template>
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
</template>
<!-- Résultat -->
<div class="static active"></div>
<script setup>
let isActive = ref(true)
let hasError = ref(false)
let classObject = computed(() => { active: isActive, 'text-danger': hasError })
</script>
<template>
<div class="static" :class="classObject"></div>
</template>
<!-- Résultat -->
<div class="static active"></div>
<script setup>
let dynamicClass = ref("foo")
</script>
<template>
<div :class="['staticClass', dynamicClass]"></div>
</template>
<!-- Résultat -->
<div class="staticClass foo"></div>
<div v-bind:class="[isActive ? 'active' : '', errorClass]"></div>
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
// Parent.vue
<Child class="baz boo" />
<!-- Résultat -->
<p class="foo bar baz boo">Hello world</p>
// Child.vue
<p class="foo bar">Hello world</p>
<script setup>
let activeColor = ref("red")
let fontSize = ref(30)
</script>
<template>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
</template>
<!-- Résultat -->
<div style="color: red; font-size: 30px;"></div>
<div :style="[baseStyles, overridingStyles]"></div>
<div :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
https://interactly.glitch.me/
<div class="foo" :class="{ bar: displayBar, baz, qux: false }"></div>
let foo = ref(false)
let bar = ref(true)
let displayBar = ref(false)
let baz = ref(false)
let qux = ref(true)
<div :style="{ backgroundColor, color: activeColor }"></div>
let activeColor = ref("red")
let color = ref("blue")
let fontSize = ref(30)
let backgroundColor = ref("white")
<script setup>
const items = [
{ message: "Foo" },
{ message: "Bar" }
]
</script>
<template>
<ul>
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
</template>
<script setup>
const items = [
{ message: "Foo" },
{ message: "Bar" }
]
</script>
<template>
<ul>
<li v-for="(item, index) in items">
#{{index}} {{ item.message }}
</li>
</ul>
</template>
<script setup>
const book = {
title: "Learn Vue",
author: "Jane Doe",
publishedAt: "2016-04-10"
}
</script>
<template>
<ul>
<li v-for="(value, key) in book">
#{{key}} {{ value }}
</li>
</ul>
</template>
⚠️ L'ordonnancement des propriétés d'un objet JavaScript n'est pas garantie
<script setup>
const book = {
title: "Learn Vue",
author: "Jane Doe",
publishedAt: "2016-04-10"
}
</script>
<template>
<ul>
<li v-for="(value, key, index) in book">
#{{index}} {{key}} : {{ value }}
</li>
</ul>
</template>
⚠️ L'ordonnancement des propriétés d'un objet JavaScript n'est pas garantie
<script setup>
import { ref } from "vue"
const fields = ref(["A", "B", "C"])
const removeField = () =>
fields.value.shift()
</script>
<template>
<input
:placeholder="'#' + i + ' - ' + field"
v-for="(field, i) in fields"
:key="i"
/>
<button @click="removeField">
Remove first field
</button>
</template>
<script setup>
import { ref } from "vue"
const fields = ref(["A", "B", "C"])
const removeField = () =>
fields.value.shift()
</script>
<template>
<input
:placeholder="'#' + i + ' - ' + field"
v-for="(field, i) in fields"
:key="field"
/>
<button @click="removeField">
Remove first field
</button>
</template>
<script setup>
import { ref } from "vue"
let letters = ref(
["J", "X", "O", "B", "U", "I"]
)
let sort = () => letters.value.sort()
</script>
<template>
<div @click="sort">
{{ letters }}
</div>
</template>
Méthodes de mutations réactives
push(), pop(), shift(), unshift(),
splice(), sort(), reverse()
<script setup>
import { ref, computed } from "vue"
const numbers = ref([1, 2, 3, 4, 5])
const even = computed(() =>
numbers.value.filter(n => n%2 === 0)
)
</script>
<template>
<li v-for="n in even">{{ n }}</li>
</template>
<Child v-for="item in items" :key="item.id" />
<Child
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
<script setup>
import { ref } from "vue"
const count = ref(0)
</script>
<template>
<p>{{ count }}</p>
<button v-on:click="count += 1">
Incrémenter !
</button>
</template>
<script setup>
import { ref } from "vue"
const count = ref(0)
function increment() {
count.value += 2
}
</script>
<template>
<p>{{ count }}</p>
<button v-on:click="increment">
Incrémenter !
</button>
</template>
<script setup>
import { ref } from "vue"
const count = ref(0)
function increment() {
count.value += 2
}
</script>
<template>
<p>{{ count }}</p>
<button v-on:click="increment">
Incrémenter !
</button>
</template>
<script setup>
import { ref } from "vue"
const count = ref(0)
function increment(delta) {
count.value += delta
}
</script>
<template>
<p>{{ count }}</p>
<button v-on:click="increment(3)">
Incrémenter !
</button>
</template>
<script setup>
import { ref } from "vue"
const events = ref([])
function log(e) {
events.value.push(e)
}
</script>
<template>
<p @click="log('p@click')">
<button @click="log('button@click')">@click</button>
<button @click.stop="log('button@click.stop')">@click.stop</button>
<button @click.once="log('button@click.once')">@click.once</button>
<button @click.prevent="log('button@click.prevent')">
@click.prevent
</button>
<button @click.prevent.once="log('button@click.prevent.once')">
@click.prevent.once
</button>
</p>
<ul v-for="event in events">
<li>{{ event }}</li>
</ul>
<button @click="events = []">
Clear
</button>
</template>
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.
<script setup>
import { ref } from "vue"
const message = ref("Hello world !")
</script>
<template>
<input v-model="message" />
<p>{{ message }}</p>
</template>
<input v-model="message" />
<input
:value="message"
@input="message = $event.target.value"
/>
<script setup>
import { ref } from "vue"
const message = ref("")
</script>
<template>
<textarea v-model="message">
</textarea>
<p style="white-space: pre-line;">
{{ message }}
</p>
</template>
<script setup>
import { ref } from "vue"
const checked = ref(true)
</script>
<template>
<input
type="checkbox"
id="checkbox"
v-model="checked"
/>
<label for="checkbox">
{{ checked }}
</label>
</template>
<script setup>
import { ref } from "vue"
const picked = ref(null)
</script>
<template>
<input type="radio" id="one"
value="Un" v-model="picked" />
<label for="one">Un</label>
<input type="radio" id="two"
value="Deux" v-model="picked" />
<label for="two">Deux</label>
<span>Choisi : {{ picked }}</span>
</template>
<script setup>
import { ref } from "vue"
const selected = ref("")
</script>
<template>
<select v-model="selected">
<option disabled value="">
Choisissez
</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Séléction : {{ selected }}</span>
</template>
<script setup>
import { ref } from "vue"
const selected = ref([])
</script>
<template>
<select v-model="selected" multiple>
<option disabled value="">
Choisissez
</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Séléction : {{ selected }}</span>
</template>
<script setup>
import { ref } from "vue"
const selected = ref("A")
const options = ref([
{ text: "Un", value: "A" },
{ text: "Deux", value: "B" },
{ text: "Trois", value: "C" }
])
</script>
<template>
<select v-model="selected">
<option v-for="option in options"
:value="option.value"
key="option.value">
{{ option.text }}
</option>
</select>
<div>Sélectionné : {{ selected }}</div>
</template>
<!-- `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>
<template>
<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>
</template>
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 ;)
http://interactly.glitch.me
<script setup>
import CounterButton from "./components/CounterButton.vue"
</script>
<template>
<CounterButton />
</template>
<script setup>
import { ref } from "vue"
const count = ref(0)
</script>
<template>
<button @click="count++">
{{ count }} clicks
</button>
</template>
<script setup>
import CounterButton from "./components/CounterButton.vue"
</script>
<template>
<CounterButton />
<CounterButton />
<CounterButton />
</template>
<script setup>
import { ref } from "vue"
const count = ref(0)
</script>
<template>
<button @click="count++">
{{ count }} clicks
</button>
</template>
import { createApp } from 'vue'
import CounterButton from "./CounterButton.vue"
const app = createApp({})
// // kebab-case
app.component("counter-button", CounterButton)
// PascalCase
app.component("CounterButton", CounterButton)
<template>
<counter-button />
<CounterButton />
</template>
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
<!-- toujours en kebab-case -->
<template>
<BlogPost post-title="Hello !" />
^^^^^^^^^^
<blog-post post-title="Hello !" />
^^^^^^^^^^
</template>
// BlogPost.vue
<script setup>
defineProps([
'postTitle'
])
</script>
<template>
<h3>{{ postTitle }}</h3>
</template>
<template>
<BlogPost title="What is love ?" />
<BlogPost title="Baby don't hurt me" />
<BlogPost title="No more" />
</template>
// BlogPost.vue
<script setup>
defineProps([
'title'
])
</script>
<template>
<h3>{{ title }}</h3>
</template>
<script setup>
import { ref } from "vue"
const posts = ref([
{ id: 1, title: `What is love ?` },
{ id: 2, title: `Baby don't hurt me` },
{ id: 3, title: `No more` }
])
</script>
<template>
<BlogPost v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</template>
// BlogPost.vue
<script setup>
defineProps([
'title'
])
</script>
<template>
<h3>{{ title }}</h3>
</template>
<script setup>
import { ref } from "vue"
const posts = ref([
{
id: 1,
title: `What is love ?`,
content: `Lorem ipsum [...]`
},
...
])
</script>
<template>
<BlogPost v-for="post in posts"
:key="post.id"
:title="post.title"
:content="post.content"
/>
</template>
// BlogPost.vue
<script setup>
defineProps([
'title',
'content'
])
</script>
<template>
<h3>{{ title }}</h3>
<div v-html="content"></div>
</template>
<script setup>
import { ref } from "vue"
const posts = ref([
...
])
</script>
<template>
<BlogPost v-for="post in posts"
:key="post.id"
:title="post.title"
:content="post.content"
:comments="post.comments"
:author="post.author"
:isSharable="post.isSharable"
:publishedAt="post.publishedAt"
/>
</template>
<script setup>
import { ref } from "vue"
const posts = ref([
...
])
</script>
<template>
<BlogPost v-for="post in posts"
:key="post.id"
:post="post"
/>
</template>
defineProps([
'title', 'likes', 'isPublished', 'commentIds', 'author'
])
defineProps({
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function, // déprécié
contactsPromise: Promise
// tout autre constructeur
})
<BlogPost
title="Lorem ipsum"
:likes="3"
:isPublished="false"
:commentIds="[1,2,3]"
:author="{name: 'Alice'}"
:callback="() => alert('Done')"
/>
defineProps({
propA: Number,
propB: [String, Number],
propC: {
type: String,
required: true
}
})
// Seulement éxécuté en dévelopment
defineProps({
propD: {
type: Number,
default: 100
},
propE: {
type: Object,
default: () => ({ message: 'hello' }) // Factory
},
propF: {
type: Array,
default: () => [] // Factory
}
})
defineProps({
propG: {
validator: value => ['A', 'B', 'C'].includes(value)
}
})
Les événements
// BlogPost.vue
<script setup>
defineProps(['title', 'content'])
</script>
<template>
<h3>{{ title }}</h3>
<div v-html="content"></div>
<button>
Like
</button>
</template>
// Remonter le like ?
<script setup>
import { ref } from "vue"
const post = ref({...})
</script>
<template>
<BlogPost
:title="post.title"
:content="post.content"
/>
</template>
// BlogPost.vue
<script setup>
defineProps(['title', 'content'])
</script>
<template>
<h3>{{ title }}</h3>
<div v-html="content"></div>
<button @click="$emit('like')">
Like
</button>
</template>
<script setup>
import { ref } from "vue"
const post = ref({...})
function receiveLike() {
...
}
</script>
<template>
<BlogPost
:title="post.title"
:content="post.content"
@like="receiveLike"
/>
</template>
// BlogPost.vue
<script setup>
defineProps(['title', 'content'])
</script>
<template>
<h3>{{ title }}</h3>
<div v-html="content"></div>
<button @click="$emit('note', 5)">
Noter bien
</button>
<button @click="$emit('note', 0)">
Noter pas bien
</button>
</template>
<script setup>
import { ref } from "vue"
const post = ref({...})
function receiveNote(payload) {
payload; // 0 ou 5
}
</script>
<template>
<BlogPost
:title="post.title"
:content="post.content"
@note="receiveNote"
/>
</template>
// BlogPost.vue
<script setup>
defineProps(['title', 'content'])
const emit = defineEmits(['note'])
function love() {
emit('note', 999)
}
</script>
<template>
<h3>{{ title }}</h3>
<div v-html="content"></div>
<button @click="love">Love</button>
</template>
<script setup>
import { ref } from "vue"
const post = ref({...})
function receiveNote(payload) {
payload; // 999
}
</script>
<template>
<BlogPost
:title="post.title"
:content="post.content"
@note="receiveNote"
/>
</template>
Props + Événements = ❤️
<script setup>
import BlogPostWriter from "./BPW.vue"
import { ref } from "vue"
const post = ref("Hello world !")
</script>
<template>
<div>{{ post }}</div>
<BlogPostWriter v-model="post" />
</template>
<script setup>
// Implémenter v-model
</script>
<template>
<div style="border: 1px solid red">
<input />
</div>
</template>
<script setup>
import BlogPostWriter from "./BPW.vue"
import { ref } from "vue"
const post = ref("Hello world !")
</script>
<template>
<div>{{ post }}</div>
<BlogPostWriter v-model="post" />
</template>
<script setup>
// Implémenter v-model
</script>
<template>
<div style="border: 1px solid red">
<input />
</div>
</template>
<BlogPostWriter
:modelValue="post"
@update:modelValue="post = $event"
/>
<script setup>
import BlogPostWriter from "./BPW.vue"
import { ref } from "vue"
const post = ref("Hello world !")
</script>
<template>
<div>{{ post }}</div>
<BlogPostWriter v-model="post" />
</template>
<BlogPostWriter
:modelValue="post"
@update:modelValue="post = $event"
/>
<script setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const update = $event => emit(
'update:modelValue',
$event.target.value
)
</script>
<template>
<div style="border: 1px solid red">
<input
:value="props.modelValue"
@input="update"
/>
</div>
</template>
<script setup>
import BlogPostWriter from "./BPW.vue"
import { ref } from "vue"
const post = ref("Hello world !")
</script>
<template>
<div>{{ post }}</div>
<BlogPostWriter v-model:text="post" />
// D'autres v-model peuvent ainsi
// être passés
</template>
<script setup>
defineProps(['text'])
const emit = defineEmits(['update:text'])
const update = $event => emit(
'update:text',
$event.target.value
)
</script>
<template>
<div style="border: 1px solid red">
<input
:value="props.text"
@input="update"
/>
</div>
</template>
Les slots
<script setup>
import AlertBox from "./AlertBox.vue"
</script>
<template>
<AlertBox>
Quelque chose s'est mal passé.
</AlertBox>
</template>
// AlertBox.vue
<template>
<div style="background:red;">
<strong>Erreur</strong>
<slot />
</div>
</template>
<script setup>
import AlertBox from "./AlertBox.vue"
</script>
<template>
<AlertBox>
Quelque chose s'est mal passé.
<template #code>
1337
</template>
</AlertBox>
</template>
// AlertBox.vue
<template>
<div style="background:red;">
<strong>Erreur</strong>
<slot />
<i v-if="$slots.code">
Code <slot name="code" />
</i>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue"
defineProps(['text'])
const textSize = computed(() =>
text.value.length
)
const sizeChanged = ref(0)
watch(textSize, () => sizeChanged.value++)
function reset {
sizeChanged.value = 0
}
</script>
<template>
{{ text }} | {{ mutationCount }}
</template>
<script>
export default {
props: ['text'],
data: () => ({
sizeChanged: 0
}),
computed: {
textSize() {
return this.text.length
}
},
watch: {
textSize() {
this.sizeChanged++
}
},
methods: {
reset() {
this.sizeChanged = 0
}
}
}
</script>
=
<script setup>
import { ref, computed, watch } from "vue"
defineProps(['text'])
const textSize = computed(() =>
text.value.length
)
const sizeChanged = ref(0)
watch(textSize, () => sizeChanged.value++)
function reset {
sizeChanged.value = 0
}
</script>
<template>
{{ text }} | {{ mutationCount }}
</template>
<script>
import { ref, computed, watch } from "vue"
export default {
props: ['text'],
setup() {
let textSize = computed(() =>
text.value.length
)
let sizeChanged = ref(0)
watch(textSize, () => sizeChanged.value++)
function reset {
sizeChanged.value = 0
}
return { text, sizeChanged }
}
}
</script>
=
<component :is="currentComponent"/>
<script setup>
import { ref } from "vue"
import ComponentA from "./ComponentA.vue"
import ComponentB from "./ComponentB.vue"
const currentComponent = ref(ComponentA)
function change() {
currentComponent.value = ComponentB
}
</script>
<script setup>
import { ref, reactive } from "vue"
const nativeObj = { count: 0 }
const reactObj = reactive({
count: 0
})
</script>
<template>
<button @click="nativeObj.count++">
native = {{ nativeObj.count }}
</button>
<button @click="reactObj.count++">
react = {{ reactObj.count }}
</button>
</template>
<script setup>
import { ref, reactive } from "vue"
const count = ref(1)
const obj = reactive({ count })
// Unwrapped : .value non requis
obj.count.value // ❌
obj.count === count.value // true
// Synchronisé
count.value++
count.value // 2
obj.count // 2
obj.count++
obj.count // 3
count.value // 3
</script>
<script setup>
import { ref, reactive } from "vue"
const array = reactive([
ref("Hello world")
])
// Wrapped : .value requis
array[0].value // "Hello world"
const map = reactive(new Map([
['key', ref("Lorem ipsum")]
]))
// Wrapped : .value requis
map.get('key').value // "Lorem ipsum"
</script>
<script setup>
import { ref, readonly } from "vue"
const foo = readonly(ref(true))
foo.value = false
foo.value // true
const bar = readonly(9) // non réactif
bar // 9
</script>
<script setup>
import { reactive, readonly }
from "vue"
const foo = readonly(reactive({
tic: "tac"
}))
foo.tic = "toc"
foo.tic // "tac"
const bar = readonly({
tic: "tac"
}) // réactif
foo.tic = "toc"
bar.tic // "tac"
</script>
Réaliser un tableau de score dont chaque ligne doit être un composant <TeamCounter>
Cahier des charges de TeamCounter :
Template voir ci-contre
Props
v-model:score : 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
Modèle
Vue
Logique
// userFooStore.js / Pinia
import { ref, computed } from "vue"
import { defineStore } from 'pinia'
export const useFooStore = defineStore('foo', () => {
const users = ref(...)
const userCount = computed(() => users.value.length)
function addUser(user) {
users.value.push(user)
}
return { users, userCount, addUser }
})
// Component.vue
<script setup>
import { ref, computed, toRefs } from "vue"
import { useFooStore } from "useFooStore.js"
const fooStore = useFooStore()
const { users, userCount } = toRefs(fooStore) // State & Getters
const { addUser } = fooStore // Actions
</script>
State
Getters
Actions
(setters)
Privée
Publique
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
const message = "Hello!"
message()
// TS 2348 :
// This expression is not callable.
// Type 'String' has no call signatures
const luck = Math.random < 0.5
// TS 2365 :
// Operator '<' cannot be applied to
// types '() => number' and 'number'.()
function hello(person, date) {
return `Hello ${person}, today is ${date}!`
}
hello("Brendan")
// TS 7006 :
// Parameter 'date' implicitly has an 'any' type
function hello(person: string, date: Date): string {
return `Hello ${person}, today is ${date.toLocaleString()}!`
}
hello("Brendan")
// TS 2345 :
// Argument of type 'string' is not assignable to parameter of type 'Date'
let foo = "dff"
foo = 42
// TS 2322 :
// Type 'number' is not assignable to type 'string'.
let bar: string | number = "dff"
bar = 42
// OK
const a: string = ""
const b: number = 7
const c: boolean = true
const d: null = null
const e: number[] = [1, 2, 3]
const f: Array<number> = [1, 2, 3]
const g: Date = new Date()
const h: Set = new Set(["LEET", 1337])
const i: Set<string> = new Set(["LEET", "1337"])
const j: Record<string, number> = { "Zero": 0, "One": 1, "Two": 2 }
function distance(A: { x: number, y: number }, B: { x: number, y: number }) {
return Math.sqrt((A.y - B.y)^2 + (A.x - B.x)^2);
}
type Point = { x: number, y: number }
function distance(A: Point, B: Point) {
return Math.sqrt((A.y - B.y)^2 + (A.x - B.x)^2);
}
<script setup lang="ts">
import { ref, computed } from "vue"
const foo = ref<number>(3)
foo.value = "Not a number" // Erreur
const bar = ref<string | null>(null)
const baz = computed<string>(() => bar.value) // Erreur
</script>